diff --git a/.dockerignore b/.dockerignore index fe232076474..f37092a78dd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,11 +11,14 @@ tmp/ contracts/node_modules examples/ -integration/node_modules + +integration/ +integration-scripts/ +integration-tests/ tools/gethnet/datadir/geth tools/clroot/db.bolt -tools/clroot/*.jsonl +tools/clroot/*.log tools/clroot/tempkeys core/sgx/target/ diff --git a/.github/actions/build-sign-publish-chainlink/action.yml b/.github/actions/build-sign-publish-chainlink/action.yml index c6cc94dfed5..3d0511efde0 100644 --- a/.github/actions/build-sign-publish-chainlink/action.yml +++ b/.github/actions/build-sign-publish-chainlink/action.yml @@ -38,13 +38,18 @@ inputs: description: When set to the string boolean value of "true", the resulting build image will be signed default: "false" required: false - cosign-private-key: description: The private key to be used with cosign to sign the image required: false - cosign-password: description: The password to decrypt the cosign private key needed to sign the image + sign-method: + description: Build image will be signed using keypair or keyless methods + default: "keypair" + required: true + verify-signature: + description: When set to the string boolean value of "true", the resulting build image signature will be verified + default: "false" required: false runs: @@ -65,7 +70,6 @@ runs: SHARED_BUILD_ARGS=$(cat << EOF COMMIT_SHA=${{ github.sha }} - ENVIRONMENT=release EOF ) @@ -108,12 +112,12 @@ runs: images: ${{ env.shared-images }} tags: ${{ env.shared-tag-list }} - - name: Build and push root docker image id: buildpush-root - uses: docker/build-push-action@a66e35b9cbcf4ad0ea91ffcaf7bbad63ad9e0229 # v2.7.0 + uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a # v2.10.0 with: push: ${{ inputs.publish }} + load: ${{ contains(inputs.publish, false) }} tags: ${{ steps.meta-root.outputs.tags }} labels: ${{ steps.meta-root.outputs.labels }} file: core/chainlink.Dockerfile @@ -121,6 +125,13 @@ runs: CHAINLINK_USER=root ${{ env.shared-build-args }} + - name: Save root image name in GITHUB_ENV + id: save-root-image-name-env + shell: sh + run: | + IMAGES_NAME_RAW=${{ fromJSON(steps.buildpush-root.outputs.metadata)['image.name'] }} + echo "root_image_name=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1)" >> $GITHUB_ENV + - name: Generate docker metadata for non-root image id: meta-nonroot uses: docker/metadata-action@e5622373a38e60fb6d795a4421e56882f2d7a681 # v3.6.2 @@ -134,9 +145,10 @@ runs: - name: Build and push non-root docker image id: buildpush-nonroot - uses: docker/build-push-action@a66e35b9cbcf4ad0ea91ffcaf7bbad63ad9e0229 # v2.7.0 + uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a # v2.10.0 with: push: ${{ inputs.publish }} + load: ${{ contains(inputs.publish, false) }} tags: ${{ steps.meta-nonroot.outputs.tags }} labels: ${{ steps.meta-nonroot.outputs.labels }} file: core/chainlink.Dockerfile @@ -144,36 +156,97 @@ runs: CHAINLINK_USER=chainlink ${{ env.shared-build-args }} + - name: Save non-root image name in GITHUB_ENV + id: save-non-root-image-name-env + shell: sh + run: | + IMAGES_NAME_RAW=${{ fromJSON(steps.buildpush-nonroot.outputs.metadata)['image.name'] }} + echo "nonroot_image_name=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1)" >> $GITHUB_ENV + + - name: Check if non-root image runs as root + id: check-nonroot-runs-root + shell: sh + env: + PUBLISH: ${{ inputs.publish }} + run: | + echo "Fail build if non-root image runs as user: root" + # if we're publishing the image, it doesn't get loaded into the local docker daemon + # so we need to pull the image into our daemon + if [ $PUBLISH = "true" ]; then + docker pull "${nonroot_image_name}" + fi + docker inspect "${nonroot_image_name}" | jq -r '.[].Config.User' | ( ! grep "root" ) + - if: inputs.sign-images == 'true' name: Install cosign uses: sigstore/cosign-installer@1e95c1de343b5b0c23352d6417ee3e48d5bcd422 # v1.4.0 with: cosign-release: 'v1.4.0' - - if: inputs.sign-images == 'true' - name: Write signing key to disk (only needed for `cosign sign --key`) + - if: inputs.sign-images == 'true' && inputs.sign-method == 'keypair' + name: Sign the published root Docker image using keypair method shell: sh - run: echo "${{ inputs.cosign-private-key }}" > cosign.key + env: + COSIGN_PASSWORD: "${{ inputs.cosign-password }}" + run: | + echo "${{ inputs.cosign-private-key }}" > cosign.key + cosign sign --key cosign.key "${{ env.root_image_name }}" + rm -f cosign.key - - if: inputs.sign-images == 'true' - name: Sign the published root Docker image + - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keypair' + name: Verify the signature of the published root Docker image using keypair + shell: sh + run: | + echo "${{ inputs.cosign-public-key }}" > cosign.key + cosign verify --key cosign.key "${{ env.root_image_name }}" + rm -f cosign.key + + - if: inputs.sign-images == 'true' && inputs.sign-method == 'keyless' + name: Sign the published root Docker image using keyless method shell: sh env: - COSIGN_PASSWORD: "${{ inputs.cosign-password }}" + COSIGN_EXPERIMENTAL: 1 run: | - IMAGES_NAME_RAW=${{ fromJSON(steps.buildpush-root.outputs.metadata)['image.name'] }} - IMAGE_NAME=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1) + cosign sign "${{ env.root_image_name }}" - cosign sign --key cosign.key "$IMAGE_NAME" + - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keyless' + name: Verify the signature of the published root Docker image using keyless + shell: sh + env: + COSIGN_EXPERIMENTAL: 1 + run: | + cosign verify "${{ env.root_image_name }}" - - if: inputs.sign-images == 'true' - name: Sign the published non-root Docker image + - if: inputs.sign-images == 'true' && inputs.sign-method == 'keypair' + name: Sign the published non-root Docker image using keypair method shell: sh env: COSIGN_PASSWORD: "${{ inputs.cosign-password }}" run: | - IMAGES_NAME_RAW=${{ fromJSON(steps.buildpush-nonroot.outputs.metadata)['image.name'] }} - IMAGE_NAME=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1) + echo "${{ inputs.cosign-private-key }}" > cosign.key + cosign sign --key cosign.key "${{ env.nonroot_image_name }}" + rm -f cosign.key - cosign sign --key cosign.key "$IMAGE_NAME" + - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keypair' + name: Verify the signature of the published non-root Docker image using keypair + shell: sh + run: | + echo "${{ inputs.cosign-public-key }}" > cosign.key + cosign verify --key cosign.key "${{ env.nonroot_image_name }}" + rm -f cosign.key + + - if: inputs.sign-images == 'true' && inputs.sign-method == 'keyless' + name: Sign the published non-root Docker image using keyless method + shell: sh + env: + COSIGN_EXPERIMENTAL: 1 + run: | + cosign sign "${{ env.nonroot_image_name }}" + - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keyless' + name: Verify the signature of the published non-root Docker image using keyless + shell: sh + env: + COSIGN_EXPERIMENTAL: 1 + run: | + cosign verify "${{ env.nonroot_image_name }}" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 86a19322ca2..6cae40e2250 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,11 @@ updates: schedule: interval: monthly open-pull-requests-limit: 10 + ignore: + # Old versions are pinned for libocr. + - dependency-name: github.com/libp2p/go-libp2p-core + - dependency-name: github.com/libp2p/go-libp2p-peerstore + - dependency-name: github.com/multiformats/go-multiaddr - package-ecosystem: npm directory: '/' schedule: diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000000..35ac0b0b3dd --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,11 @@ +# GitHub Workflows + +## Required Checks and Path Filters + +We want to run certain workflows only when certain file paths change. We can accomplish this with [path based filtering on GitHub actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore). The problem that we run into is that we have certain required checks on GitHub that will not run or pass if we have path based filtering that never executes the workflow. + +The [solution that GitHub recommends](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks) is to create a "dummy" workflow with the same workflow name and job names as the required workflow/jobs with the jobs running a command to simply exit zero immediately to indicate success. + +### Solution + +If your workflow is named `solidity.yml`, create a `solidity-paths-ignore.yml` file with the same workflow name, event triggers (except for the path filters, use `paths-ignore` instead of `paths`), same job names, and then in the steps feel free to echo a command or explicitly `exit 0` to make sure it passes. See the workflow file names with the `-paths-ignore.yml` suffix in this directory for examples. diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 60e8067a305..1ca03a6ff85 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -57,7 +57,7 @@ jobs: ref: ${{ github.event.inputs.cl_ref }} - uses: actions/setup-go@v2 with: - go-version: '1.17.2' + go-version: ~1.17 - name: Replace Solana deps manual flow if: ${{ github.event.inputs.dep_solana_sha }} run: | @@ -99,6 +99,7 @@ jobs: with: context: . file: core/chainlink.Dockerfile - build-args: COMMIT_SHA=${{ github.sha }},ENVIRONMENT=release + # comma separated like: KEY1=VAL1,KEY2=VAL2,... + build-args: COMMIT_SHA=${{ github.sha }} tags: 795953128386.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/chainlink:custom.${{ github.sha }} push: true diff --git a/.github/workflows/build-publish-release-ignore.yml b/.github/workflows/build-publish-release-ignore.yml new file mode 100644 index 00000000000..41b3b6437fc --- /dev/null +++ b/.github/workflows/build-publish-release-ignore.yml @@ -0,0 +1,19 @@ +## +# This workflow needs to be ran because `build-sign-publish-chainlink` is +# a required check but we do not want our release branches to build and +# publish images. Instead we use tags. +# If the workflow does not run, the required check will never pass. +## + +name: 'Build Chainlink and Publish' + +on: + push: + branches: + - release/* + +jobs: + build-sign-publish-chainlink: + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required"' diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 4bc6dea7300..d7dedb17ea6 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -8,7 +8,6 @@ on: branches: - master - develop - - 'release/*' jobs: build-sign-publish-chainlink: @@ -29,5 +28,8 @@ jobs: aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} aws-region: ${{ secrets.AWS_REGION }} sign-images: true + sign-method: 'keypair' cosign-private-key: ${{ secrets.COSIGN_PRIVATE_KEY }} + cosign-public-key: ${{ secrets.COSIGN_PUBLIC_KEY }} cosign-password: ${{ secrets.COSIGN_PASSWORD }} + verify-signature: true diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/ci-core.yml similarity index 67% rename from .github/workflows/continuous-integration-workflow.yml rename to .github/workflows/ci-core.yml index 870bbd126c6..c4a16331bbb 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/ci-core.yml @@ -1,13 +1,13 @@ -name: CI +name: CI Core -on: [push] +on: push jobs: core: strategy: fail-fast: false matrix: - cmd: ['go_core_tests', 'go_core_race_tests'] + cmd: ["go_core_tests", "go_core_race_tests"] name: Core Tests runs-on: ubuntu-latest env: @@ -35,7 +35,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.17 + go-version: ~1.17 - name: Cache Go vendor packages uses: actions/cache@v2 with: @@ -67,30 +67,3 @@ jobs: with: args: logs ${{ job.services.postgres.id }} - prepublish_npm: - name: Prepublish NPM - runs-on: ubuntu-latest - steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: "16" - - name: Yarn cache - uses: actions/cache@v2 - env: - cache-name: yarn-cache - with: - path: | - ~/.npm - ~/.cache - **/node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - run: yarn install --frozen-lockfile - - name: Run prepublish NPM test - run: ./tools/ci/prepublish_npm_test diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index b38c1221968..4c14cfbca26 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -3,8 +3,8 @@ name: Code Quality on: push: branches: - - auto - - try + - staging + - trying - rollup pull_request: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b79d6ad52ba..da7f9f80abc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -4,8 +4,8 @@ on: push: branches: - develop - - auto - - try + - staging + - trying - rollup pull_request: # The branches below must be a subset of the branches above @@ -33,7 +33,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.17 + go-version: ~1.17 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dependency-check-paths-ignore.yml b/.github/workflows/dependency-check-paths-ignore.yml new file mode 100644 index 00000000000..06c7aa352cd --- /dev/null +++ b/.github/workflows/dependency-check-paths-ignore.yml @@ -0,0 +1,24 @@ +## +# This workflow needs to be ran in case it is a required check and +# we conditionally only run the `dependency-check` workflow when certain +# paths change. +# If the workflow does not run, and it is ever marked as required, +# then the check will never pass. +# This is GitHub's workaround: +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#example +## + +name: Dependency Vulnerability Check + +on: + push: + paths-ignore: + - '**/go.mod' + - '**/go.sum' +jobs: + Go: + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required" ' + + diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index be6efa9b13b..087cfb451fc 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -1,6 +1,15 @@ +## +# NOTE: any changes to the event triggers or the paths here should be reflected in: +# dependency-check-paths-ignore.yml +## + name: Dependency Vulnerability Check -on: [push] +on: + push: + paths: + - '**/go.mod' + - '**/go.sum' jobs: Go: runs-on: ubuntu-latest @@ -11,7 +20,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.17 + go-version: ~1.17 id: go - name: Write Go Modules list diff --git a/.github/workflows/golangci-lint-paths-ignore.yml b/.github/workflows/golangci-lint-paths-ignore.yml new file mode 100644 index 00000000000..d5886257a5a --- /dev/null +++ b/.github/workflows/golangci-lint-paths-ignore.yml @@ -0,0 +1,29 @@ +## +# This workflow needs to be ran because `lint` is a required check and +# we conditionally only run the `golangci-lint` workflow when certain paths +# change. +# If the workflow does not run, the required check will never pass. This is +# GitHub's workaround: +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#example +## + +name: golangci-lint + +on: + push: + branches: + - staging + - trying + - rollup + paths-ignore: + - '**.go' + pull_request: + paths-ignore: + - '**.go' + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required" ' diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ed51b924fdf..1d80584e402 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -1,17 +1,27 @@ +## +# NOTE: any changes to the event triggers or the paths here should be reflected in: +# golangci-lint-paths-ignore.yml +## + name: golangci-lint on: push: branches: - - auto - - try + - staging + - trying - rollup + paths: + - '**.go' pull_request: + paths: + - '**.go' jobs: golangci: name: lint runs-on: ubuntu-latest + go: '1.17' steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b0ed2da6298..76c570fc693 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,9 +1,23 @@ name: Integration Tests -on: [pull_request] +on: + pull_request: jobs: + changes: + runs-on: ubuntu-latest + outputs: + src: ${{ steps.changes.outputs.src }} + steps: + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + src: + - '**/*.go' build-chainlink: name: Build Chainlink Image runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.src == 'true' }} steps: - name: Checkout the repo uses: actions/checkout@v2 @@ -25,14 +39,15 @@ jobs: with: context: . file: core/chainlink.Dockerfile - build-args: COMMIT_SHA=${{ github.sha }},ENVIRONMENT=release + # comma separated like: KEY1=VAL1,KEY2=VAL2,... + build-args: COMMIT_SHA=${{ github.sha }} tags: 795953128386.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/chainlink:latest.${{ github.sha }} push: true - smoke: - name: Smoke Tests + name: ETH Smoke Tests runs-on: ubuntu-latest - needs: build-chainlink + needs: [changes, build-chainlink] + if: ${{ needs.changes.outputs.src == 'true' }} env: CHAINLINK_IMAGE: 795953128386.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/chainlink CHAINLINK_VERSION: latest.${{ github.sha }} @@ -95,3 +110,36 @@ jobs: with: name: test-logs path: ./integration-tests/logs + solana: + name: Solana Tests + needs: [changes, build-chainlink] + if: ${{ needs.changes.outputs.src == 'true' }} + uses: smartcontractkit/chainlink-solana/.github/workflows/e2e.yml@develop + with: + repo_name: smartcontractkit/chainlink-solana + ref: develop + cl_repo: 795953128386.dkr.ecr.us-west-2.amazonaws.com/chainlink + cl_image: latest.${{ github.sha }} + secrets: + QA_AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + QA_AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + QA_AWS_REGION: ${{ secrets.AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.KUBECONFIG }} + terra: + name: Terra Tests + needs: [changes, build-chainlink] + if: ${{ needs.changes.outputs.src == 'true' }} + uses: smartcontractkit/chainlink-terra/.github/workflows/e2e.yml@main + with: + repo_name: smartcontractkit/chainlink-terra + ref: develop + cl_repo: 795953128386.dkr.ecr.us-west-2.amazonaws.com/chainlink + cl_image: latest.${{ github.sha }} + secrets: + QA_AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + QA_AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + QA_AWS_REGION: ${{ secrets.AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.KUBECONFIG }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/operator-ui-paths-ignore.yml b/.github/workflows/operator-ui-paths-ignore.yml new file mode 100644 index 00000000000..1cdf148713b --- /dev/null +++ b/.github/workflows/operator-ui-paths-ignore.yml @@ -0,0 +1,33 @@ +## +# This workflow needs to be ran because `Operator UI Tests` is a required check +# and we conditionally only run the `Operator UI` workflow when certain paths +# change. +# If the workflow does not run, the required check will never pass. This is +# GitHub's workaround: +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#example +## + +name: Operator UI + +on: + push: + branches: + - staging + - trying + - rollup + paths-ignore: + - 'operator_ui/**' + - yarn.lock + - 'tools/ci/**' + pull_request: + paths-ignore: + - 'operator_ui/**' + - yarn.lock + - 'tools/ci/**' + +jobs: + operator-ui: + name: Operator UI Tests + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required" ' diff --git a/.github/workflows/operator-ui.yml b/.github/workflows/operator-ui.yml index 2d8b0f446ff..52698be7cdd 100644 --- a/.github/workflows/operator-ui.yml +++ b/.github/workflows/operator-ui.yml @@ -1,12 +1,25 @@ +## +# NOTE: any changes to the event triggers or the paths here should be reflected in: +# operator-ui-paths-ignore.yml +## + name: Operator UI on: push: branches: - - auto - - try + - staging + - trying - rollup + paths: + - 'operator_ui/**' + - yarn.lock + - 'tools/ci/**' pull_request: + paths: + - 'operator_ui/**' + - yarn.lock + - 'tools/ci/**' jobs: operator-ui: diff --git a/.github/workflows/solidity-paths-ignore.yml b/.github/workflows/solidity-paths-ignore.yml new file mode 100644 index 00000000000..19374d216fc --- /dev/null +++ b/.github/workflows/solidity-paths-ignore.yml @@ -0,0 +1,38 @@ +## +# This workflow needs to be ran because `Solidity` is a required check and we +# conditionally only run the `Solidity` workflow when certain paths change. +# If the workflow does not run, the required check will never pass. This is +# GitHub's workaround: +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#example +## + +name: Solidity + +on: + push: + branches: + - staging + - trying + - rollup + paths-ignore: + - 'contracts/**' + - yarn.lock + - 'tools/ci/**' + pull_request: + paths-ignore: + - 'contracts/**' + - yarn.lock + - 'tools/ci/**' + +jobs: + solidity-coverage: + name: Solidity Coverage + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required" ' + solidity: + name: Solidity + runs-on: ubuntu-latest + steps: + - run: 'echo "No job required" ' + diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 199fda94569..be209a80986 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -1,12 +1,25 @@ +## +# NOTE: any changes to the event triggers or the paths here should be reflected in: +# solidity-paths-ignore.yml +## + name: Solidity on: push: branches: - - auto - - try + - staging + - trying - rollup + paths: + - 'contracts/**' + - yarn.lock + - 'tools/ci/**' pull_request: + paths: + - 'contracts/**' + - yarn.lock + - 'tools/ci/**' jobs: solidity-coverage: @@ -70,3 +83,5 @@ jobs: run: ./tools/ci/check_solc_hashes - name: Run tests run: ./tools/ci/solidity_test_hardhat + - name: Run prepublish NPM test + run: ./tools/ci/prepublish_npm_test diff --git a/.golangci.yml b/.golangci.yml index ce1618f9d2b..c5a183ccad0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,7 @@ linters: - gosec - misspell - rowserrcheck + - errorlint disable: # These are all considered deprecated: https://github.com/golangci/golangci-lint/issues/1841 - deadcode @@ -28,6 +29,9 @@ linters-settings: govet: # report about shadowed variables check-shadowing: true + errorlint: + # Allow formatting of errors without %w + errorf: false revive: confidence: 0.8 rules: diff --git a/CODEOWNERS b/CODEOWNERS index 184a7c5bacc..9cf40a4135d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,6 +5,7 @@ /core/chains/evm/headtracker @pinebit @samsondav /core/chains/evm/log @jmank88 @spooktheducks /core/chains/evm @samsondav +/core/chains/terra @connorwstein @jmank88 # Services /core/services/cron @spooktheducks @samsondav diff --git a/GNUmakefile b/GNUmakefile index 9137780adb1..656f0df15c8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -94,7 +94,7 @@ telemetry-protobuf: $(telemetry-protobuf) ## Generate telemetry protocol buffers test_smoke: # Run integration smoke tests. ginkgo -v -r --junit-report=tests-smoke-report.xml \ --keep-going --trace --randomize-all --randomize-suites \ - -tags smoke --progress $(args) ./integration-tests/smoke + --progress $(args) ./integration-tests/smoke help: diff --git a/README.md b/README.md index e8d2d832bb3..3376b070122 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ regarding Chainlink social accounts, news, and networking. 1. [Install Go 1.17](https://golang.org/doc/install), and add your GOPATH's [bin directory to your PATH](https://golang.org/doc/code.html#GOPATH) - Example Path for macOS `export PATH=$GOPATH/bin:$PATH` & `export GOPATH=/Users/$USER/go` -2. Install [NodeJS 12.22](https://nodejs.org/en/download/package-manager/) & [Yarn](https://yarnpkg.com/lang/en/docs/install/) - - It might be easier long term to use [nvm](https://nodejs.org/en/download/package-manager/#nvm) to switch between node versions for different projects: `nvm install 12.22 && nvm use 12.22` +2. Install [NodeJS](https://nodejs.org/en/download/package-manager/) & [Yarn](https://yarnpkg.com/lang/en/docs/install/). See the current version in `package.json` at the root of this repo under the `engines.node` key. + - It might be easier long term to use [nvm](https://nodejs.org/en/download/package-manager/#nvm) to switch between node versions for different projects. For example, assuming $NODE_VERSION was set to a valid version of NodeJS, you could run: `nvm install $NODE_VERSION && nvm use $NODE_VERSION` 3. Install [Postgres (>= 11.x)](https://wiki.postgresql.org/wiki/Detailed_installation_guides). - You should [configure Postgres](https://www.postgresql.org/docs/12/ssl-tcp.html) to use SSL connection (or for testing you can set `?sslmode=disable` in your Postgres query string). 4. Ensure you have Python 3 installed (this is required by [solc-select](https://github.com/crytic/solc-select) which is needed to compile solidity contracts) @@ -139,9 +139,10 @@ go generate ./... ```bash export DATABASE_URL=postgresql://127.0.0.1:5432/chainlink_test?sslmode=disable -export CHAINLINK_DEV=true # You may wish to use something like direnv to manage env variables instead of setting directly ``` +Note: Other environment variables should not be set for all tests to pass + 6. Drop/Create test database and run migrations: ``` @@ -153,9 +154,15 @@ If you do end up modifying the migrations for the database, you will need to rer 7. Run tests: ```bash -go test -parallel=1 ./... +go test ./... ``` +### Notes + +- The `parallel` flag can be used to limit CPU usage, for running tests in the background (`-parallel=4`) - the default is `GOMAXPROCS` +- The `p` flag can be used to limit the number of _packages_ tested concurrently, if they are interferring with one another (`-p=1`) +- The `-short` flag skips tests which depend on the database, for quickly spot checking simpler tests in around one minute (you may still need a phony env var to pass some validation: `DATABASE_URL=_test`) + ### Solidity Development Inside the `contracts/` directory: diff --git a/VERSION b/VERSION index 6085e946503..f0bb29e7638 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.1 +1.3.0 diff --git a/bors.toml b/bors.toml index 27ca2edc0bb..48c26977de3 100644 --- a/bors.toml +++ b/bors.toml @@ -1,12 +1,17 @@ +# https://github.com/bors-ng/bors-ng/issues/730 status = [ - "Core Tests", - "Explorer", - "Geth Integration Tests", - "Operator UI", - "Parity Integration tests", - "Solidity", - "Yarn Lint", + "lint", + "Core Tests (go_core_tests)", + "Core Tests (go_core_race_tests)", + "Solidity", + "Operator UI Tests", + "Prettier Formatting" ] -block_labels = [ "do-not-merge-yet" ] +block_labels = [ "S-do-not-merge-yet" ] timeout_sec = 3600 # one hour required_approvals = 1 +up_to_date_approvals = true +delete_merged_branches = true +update_base_for_deletes = true +use_codeowners = true +use_squash_merge = true diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index b48f784a97d..20fc8e6c0b2 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - Ignore status update in `ArbitrumSequencerUptimeFeed` if incoming update has stale timestamp +- Revert to using current Arbitrum seq status flag in `ArbitrumSequencerUptimeFeed` ## 0.4.0 - 2022-02-07 diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index cd676c04293..809b95fad39 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -1,9 +1,9 @@ -import '@typechain/hardhat' import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-etherscan' import '@nomiclabs/hardhat-waffle' -import 'hardhat-contract-sizer' +import '@typechain/hardhat' import 'hardhat-abi-exporter' +import 'hardhat-contract-sizer' import 'solidity-coverage' const COMPILER_SETTINGS = { diff --git a/contracts/scripts/native_solc8_compile b/contracts/scripts/native_solc8_compile index 9c5913a48fd..89c7e310ef1 100755 --- a/contracts/scripts/native_solc8_compile +++ b/contracts/scripts/native_solc8_compile @@ -12,7 +12,4 @@ solc-select use 0.8.6 solc --overwrite --optimize --optimize-runs 1000000 --metadata-hash none \ -o $SCRIPTPATH/solc/v0.8 \ --abi --bin --allow-paths $SCRIPTPATH/src/v0.8,$SCRIPTPATH/src/v0.8/dev,$SCRIPTPATH/src/v0.8/interfaces,$SCRIPTPATH/src/v0.8/mocks,$SCRIPTPATH/src/v0.8/tests,$SCRIPTPATH/src/v0.8/vendor \ - $SCRIPTPATH/src/v0.8/$1 -# Remove the error types from the abi, not yet supported by abigen -cat $SCRIPTPATH/solc/v0.8/$(basename $1 .sol).abi | jq '. | del(.[] | select(.type == "error"))' > tmp -mv tmp $SCRIPTPATH/solc/v0.8/$(basename $1 .sol).abi + $SCRIPTPATH/src/v0.8/$1 \ No newline at end of file diff --git a/contracts/scripts/native_solc_compile_all b/contracts/scripts/native_solc_compile_all index cb4abc702e0..fdb30cd30db 100755 --- a/contracts/scripts/native_solc_compile_all +++ b/contracts/scripts/native_solc_compile_all @@ -29,6 +29,7 @@ $SCRIPTPATH/native_solc7_compile tests/VRFCoordinatorMock.sol # Keeper $SCRIPTPATH/native_solc7_compile KeeperRegistry.sol +$SCRIPTPATH/native_solc7_compile KeeperRegistryVB.sol $SCRIPTPATH/native_solc7_compile UpkeepRegistrationRequests.sol $SCRIPTPATH/native_solc7_compile tests/UpkeepPerformCounterRestrictive.sol $SCRIPTPATH/native_solc7_compile tests/UpkeepCounter.sol @@ -36,9 +37,6 @@ $SCRIPTPATH/native_solc7_compile tests/UpkeepCounter.sol # Aggregators $SCRIPTPATH/native_solc8_compile interfaces/AggregatorV2V3Interface.sol -# Feeds -$SCRIPTPATH/native_solc7_compile tests/DerivedPriceFeed.sol - $SCRIPTPATH/native_solc8_compile Chainlink.sol $SCRIPTPATH/native_solc8_compile ChainlinkClient.sol $SCRIPTPATH/native_solc8_compile VRFRequestIDBase.sol @@ -48,16 +46,23 @@ $SCRIPTPATH/native_solc8_compile tests/VRFRequestIDBaseTestHelper.sol $SCRIPTPATH/native_solc8_compile mocks/VRFCoordinatorMock.sol # VRF V2 -$SCRIPTPATH/native_solc8_compile dev/VRFConsumerBaseV2.sol +$SCRIPTPATH/native_solc8_compile VRFConsumerBaseV2.sol $SCRIPTPATH/native_solc8_compile tests/VRFConsumerV2.sol $SCRIPTPATH/native_solc8_compile tests/VRFMaliciousConsumerV2.sol $SCRIPTPATH/native_solc8_compile tests/VRFTestHelper.sol $SCRIPTPATH/native_solc8_compile tests/VRFV2RevertingExample.sol +$SCRIPTPATH/native_solc8_compile dev/BatchBlockhashStore.sol + # Make sure the example consumers compile $SCRIPTPATH/native_solc8_compile tests/VRFExternalSubOwnerExample.sol $SCRIPTPATH/native_solc8_compile tests/VRFSingleConsumerExample.sol $SCRIPTPATH/native_solc8_compile tests/VRFOwnerlessConsumerExample.sol +$SCRIPTPATH/native_solc8_compile tests/VRFLoadTestOwnerlessConsumer.sol +$SCRIPTPATH/native_solc8_compile tests/VRFLoadTestExternalSubOwner.sol $SCRIPTPATH/native_solc8_compile tests/VRFCoordinatorV2TestHelper.sol $SCRIPTPATH/native_solc8_compile dev/VRFCoordinatorV2.sol + +# Feeds +$SCRIPTPATH/native_solc8_compile dev/DerivedPriceFeed.sol diff --git a/contracts/src/v0.7/KeeperRegistryVB.sol b/contracts/src/v0.7/KeeperRegistryVB.sol new file mode 100644 index 00000000000..0df01713ab6 --- /dev/null +++ b/contracts/src/v0.7/KeeperRegistryVB.sol @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "./interfaces/AggregatorV3Interface.sol"; +import "./interfaces/LinkTokenInterface.sol"; +import "./interfaces/KeeperCompatibleInterface.sol"; +import "./interfaces/KeeperRegistryInterface.sol"; +import "./interfaces/TypeAndVersionInterface.sol"; +import "./vendor/SafeMathChainlink.sol"; +import "./vendor/Address.sol"; +import "./vendor/Pausable.sol"; +import "./vendor/ReentrancyGuard.sol"; +import "./vendor/SignedSafeMath.sol"; +import "./vendor/SafeMath96.sol"; +import "./KeeperBase.sol"; +import "./ConfirmedOwner.sol"; + +/** + * @notice Registry for adding work for Chainlink Keepers to perform on client + * contracts. Clients must support the Upkeep interface. + */ +contract KeeperRegistryVB is + TypeAndVersionInterface, + ConfirmedOwner, + KeeperBase, + ReentrancyGuard, + Pausable, + KeeperRegistryExecutableInterface +{ + using Address for address; + using SafeMathChainlink for uint256; + using SafeMath96 for uint96; + using SignedSafeMath for int256; + + address private constant ZERO_ADDRESS = address(0); + address private constant IGNORE_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + bytes4 private constant CHECK_SELECTOR = KeeperCompatibleInterface.checkUpkeep.selector; + bytes4 private constant PERFORM_SELECTOR = KeeperCompatibleInterface.performUpkeep.selector; + uint256 private constant CALL_GAS_MAX = 5_000_000; + uint256 private constant CALL_GAS_MIN = 2_300; + uint256 private constant CANCELATION_DELAY = 50; + uint256 private constant CUSHION = 5_000; + uint256 private constant REGISTRY_GAS_OVERHEAD = 80_000; + uint256 private constant PPB_BASE = 1_000_000_000; + uint64 private constant UINT64_MAX = 2**64 - 1; + uint96 private constant LINK_TOTAL_SUPPLY = 1e27; + + uint256 private s_upkeepCount; + uint256[] private s_canceledUpkeepList; + address[] private s_keeperList; + mapping(uint256 => Upkeep) private s_upkeep; + mapping(address => KeeperInfo) private s_keeperInfo; + mapping(address => address) private s_proposedPayee; + mapping(uint256 => bytes) private s_checkData; + Config private s_config; + uint256 private s_fallbackGasPrice; // not in config object for gas savings + uint256 private s_fallbackLinkPrice; // not in config object for gas savings + uint256 private s_expectedLinkBalance; + + LinkTokenInterface public immutable LINK; + AggregatorV3Interface public immutable LINK_ETH_FEED; + AggregatorV3Interface public immutable FAST_GAS_FEED; + + address private s_registrar; + + /** + * @notice versions: + * - KeeperRegistry 1.1.0: added flatFeeMicroLink + * - KeeperRegistry 1.0.0: initial release + */ + string public constant override typeAndVersion = "KeeperRegistry 1.1.0"; + + struct Upkeep { + address target; + uint32 executeGas; + uint96 balance; + address admin; + uint64 maxValidBlocknumber; + address lastKeeper; + } + + struct KeeperInfo { + address payee; + uint96 balance; + bool active; + } + + struct Config { + uint32 paymentPremiumPPB; + uint32 flatFeeMicroLink; // min 0.000001 LINK, max 4294 LINK + uint24 blockCountPerTurn; + uint32 checkGasLimit; + uint24 stalenessSeconds; + uint16 gasCeilingMultiplier; + bool mustTakeTurns; + } + + struct PerformParams { + address from; + uint256 id; + bytes performData; + uint256 maxLinkPayment; + uint256 gasLimit; + uint256 adjustedGasWei; + uint256 linkEth; + } + + event UpkeepRegistered(uint256 indexed id, uint32 executeGas, address admin); + event UpkeepPerformed( + uint256 indexed id, + bool indexed success, + address indexed from, + uint96 payment, + bytes performData + ); + event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight); + event FundsAdded(uint256 indexed id, address indexed from, uint96 amount); + event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); + event ConfigSet( + uint32 paymentPremiumPPB, + uint24 blockCountPerTurn, + uint32 checkGasLimit, + uint24 stalenessSeconds, + uint16 gasCeilingMultiplier, + uint256 fallbackGasPrice, + uint256 fallbackLinkPrice, + bool mustTakeTurns + ); + event FlatFeeSet(uint32 flatFeeMicroLink); + event KeepersUpdated(address[] keepers, address[] payees); + event PaymentWithdrawn(address indexed keeper, uint256 indexed amount, address indexed to, address payee); + event PayeeshipTransferRequested(address indexed keeper, address indexed from, address indexed to); + event PayeeshipTransferred(address indexed keeper, address indexed from, address indexed to); + event RegistrarChanged(address indexed from, address indexed to); + + /** + * @param link address of the LINK Token + * @param linkEthFeed address of the LINK/ETH price feed + * @param fastGasFeed address of the Fast Gas price feed + * @param paymentPremiumPPB payment premium rate oracles receive on top of + * being reimbursed for gas, measured in parts per billion + * @param flatFeeMicroLink flat fee paid to oracles for performing upkeeps, + * priced in MicroLink; can be used in conjunction with or independently of + * paymentPremiumPPB + * @param blockCountPerTurn number of blocks each oracle has during their turn to + * perform upkeep before it will be the next keeper's turn to submit + * @param checkGasLimit gas limit when checking for upkeep + * @param stalenessSeconds number of seconds that is allowed for feed data to + * be stale before switching to the fallback pricing + * @param gasCeilingMultiplier multiplier to apply to the fast gas feed price + * when calculating the payment ceiling for keepers + * @param fallbackGasPrice gas price used if the gas price feed is stale + * @param fallbackLinkPrice LINK price used if the LINK price feed is stale + */ + constructor( + address link, + address linkEthFeed, + address fastGasFeed, + uint32 paymentPremiumPPB, + uint32 flatFeeMicroLink, + uint24 blockCountPerTurn, + uint32 checkGasLimit, + uint24 stalenessSeconds, + uint16 gasCeilingMultiplier, + uint256 fallbackGasPrice, + uint256 fallbackLinkPrice, + bool mustTakeTurns + ) ConfirmedOwner(msg.sender) { + LINK = LinkTokenInterface(link); + LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed); + FAST_GAS_FEED = AggregatorV3Interface(fastGasFeed); + + setConfig( + paymentPremiumPPB, + flatFeeMicroLink, + blockCountPerTurn, + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + fallbackGasPrice, + fallbackLinkPrice, + mustTakeTurns + ); + } + + // ACTIONS + + /** + * @notice adds a new upkeep + * @param target address to perform upkeep on + * @param gasLimit amount of gas to provide the target contract when + * performing upkeep + * @param admin address to cancel upkeep and withdraw remaining funds + * @param checkData data passed to the contract when checking for upkeep + */ + function registerUpkeep( + address target, + uint32 gasLimit, + address admin, + bytes calldata checkData + ) external override onlyOwnerOrRegistrar returns (uint256 id) { + require(target.isContract(), "target is not a contract"); + require(gasLimit >= CALL_GAS_MIN, "min gas is 2300"); + require(gasLimit <= CALL_GAS_MAX, "max gas is 5000000"); + + id = s_upkeepCount; + s_upkeep[id] = Upkeep({ + target: target, + executeGas: gasLimit, + balance: 0, + admin: admin, + maxValidBlocknumber: UINT64_MAX, + lastKeeper: address(0) + }); + s_checkData[id] = checkData; + s_upkeepCount++; + + emit UpkeepRegistered(id, gasLimit, admin); + + return id; + } + + /** + * @notice simulated by keepers via eth_call to see if the upkeep needs to be + * performed. If upkeep is needed, the call then simulates performUpkeep + * to make sure it succeeds. Finally, it returns the success status along with + * payment information and the perform data payload. + * @param id identifier of the upkeep to check + * @param from the address to simulate performing the upkeep from + */ + function checkUpkeep(uint256 id, address from) + external + override + whenNotPaused + cannotExecute + returns ( + bytes memory performData, + uint256 maxLinkPayment, + uint256 gasLimit, + uint256 adjustedGasWei, + uint256 linkEth + ) + { + Upkeep memory upkeep = s_upkeep[id]; + + bytes memory callData = abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[id]); + (bool success, bytes memory result) = upkeep.target.call{gas: s_config.checkGasLimit}(callData); + + if (!success) { + string memory upkeepRevertReason = getRevertMsg(result); + string memory reason = string(abi.encodePacked("call to check target failed: ", upkeepRevertReason)); + revert(reason); + } + + (success, performData) = abi.decode(result, (bool, bytes)); + require(success, "upkeep not needed"); + + PerformParams memory params = generatePerformParams(from, id, performData, false); + prePerformUpkeep(upkeep, params.from, params.maxLinkPayment); + + return (performData, params.maxLinkPayment, params.gasLimit, params.adjustedGasWei, params.linkEth); + } + + /** + * @notice executes the upkeep with the perform data returned from + * checkUpkeep, validates the keeper's permissions, and pays the keeper. + * @param id identifier of the upkeep to execute the data with. + * @param performData calldata parameter to be passed to the target upkeep. + */ + function performUpkeep(uint256 id, bytes calldata performData) external override returns (bool success) { + return performUpkeepWithParams(generatePerformParams(msg.sender, id, performData, true)); + } + + /** + * @notice prevent an upkeep from being performed in the future + * @param id upkeep to be canceled + */ + function cancelUpkeep(uint256 id) external override { + uint64 maxValid = s_upkeep[id].maxValidBlocknumber; + bool notCanceled = maxValid == UINT64_MAX; + bool isOwner = msg.sender == owner(); + require(notCanceled || (isOwner && maxValid > block.number), "too late to cancel upkeep"); + require(isOwner || msg.sender == s_upkeep[id].admin, "only owner or admin"); + + uint256 height = block.number; + if (!isOwner) { + height = height.add(CANCELATION_DELAY); + } + s_upkeep[id].maxValidBlocknumber = uint64(height); + if (notCanceled) { + s_canceledUpkeepList.push(id); + } + + emit UpkeepCanceled(id, uint64(height)); + } + + /** + * @notice adds LINK funding for an upkeep by transferring from the sender's + * LINK balance + * @param id upkeep to fund + * @param amount number of LINK to transfer + */ + function addFunds(uint256 id, uint96 amount) external override { + require(s_upkeep[id].maxValidBlocknumber == UINT64_MAX, "upkeep must be active"); + s_upkeep[id].balance = s_upkeep[id].balance.add(amount); + s_expectedLinkBalance = s_expectedLinkBalance.add(amount); + LINK.transferFrom(msg.sender, address(this), amount); + emit FundsAdded(id, msg.sender, amount); + } + + /** + * @notice uses LINK's transferAndCall to LINK and add funding to an upkeep + * @dev safe to cast uint256 to uint96 as total LINK supply is under UINT96MAX + * @param sender the account which transferred the funds + * @param amount number of LINK transfer + */ + function onTokenTransfer( + address sender, + uint256 amount, + bytes calldata data + ) external { + require(msg.sender == address(LINK), "only callable through LINK"); + require(data.length == 32, "data must be 32 bytes"); + uint256 id = abi.decode(data, (uint256)); + require(s_upkeep[id].maxValidBlocknumber == UINT64_MAX, "upkeep must be active"); + + s_upkeep[id].balance = s_upkeep[id].balance.add(uint96(amount)); + s_expectedLinkBalance = s_expectedLinkBalance.add(amount); + + emit FundsAdded(id, sender, uint96(amount)); + } + + /** + * @notice removes funding from a canceled upkeep + * @param id upkeep to withdraw funds from + * @param to destination address for sending remaining funds + */ + function withdrawFunds(uint256 id, address to) external validateRecipient(to) { + require(s_upkeep[id].admin == msg.sender, "only callable by admin"); + require(s_upkeep[id].maxValidBlocknumber <= block.number, "upkeep must be canceled"); + + uint256 amount = s_upkeep[id].balance; + s_upkeep[id].balance = 0; + s_expectedLinkBalance = s_expectedLinkBalance.sub(amount); + emit FundsWithdrawn(id, amount, to); + + LINK.transfer(to, amount); + } + + /** + * @notice recovers LINK funds improperly transferred to the registry + * @dev In principle this function’s execution cost could exceed block + * gas limit. However, in our anticipated deployment, the number of upkeeps and + * keepers will be low enough to avoid this problem. + */ + function recoverFunds() external onlyOwner { + uint256 total = LINK.balanceOf(address(this)); + LINK.transfer(msg.sender, total.sub(s_expectedLinkBalance)); + } + + /** + * @notice withdraws a keeper's payment, callable only by the keeper's payee + * @param from keeper address + * @param to address to send the payment to + */ + function withdrawPayment(address from, address to) external validateRecipient(to) { + KeeperInfo memory keeper = s_keeperInfo[from]; + require(keeper.payee == msg.sender, "only callable by payee"); + + s_keeperInfo[from].balance = 0; + s_expectedLinkBalance = s_expectedLinkBalance.sub(keeper.balance); + emit PaymentWithdrawn(from, keeper.balance, to, msg.sender); + + LINK.transfer(to, keeper.balance); + } + + /** + * @notice proposes the safe transfer of a keeper's payee to another address + * @param keeper address of the keeper to transfer payee role + * @param proposed address to nominate for next payeeship + */ + function transferPayeeship(address keeper, address proposed) external { + require(s_keeperInfo[keeper].payee == msg.sender, "only callable by payee"); + require(proposed != msg.sender, "cannot transfer to self"); + + if (s_proposedPayee[keeper] != proposed) { + s_proposedPayee[keeper] = proposed; + emit PayeeshipTransferRequested(keeper, msg.sender, proposed); + } + } + + /** + * @notice accepts the safe transfer of payee role for a keeper + * @param keeper address to accept the payee role for + */ + function acceptPayeeship(address keeper) external { + require(s_proposedPayee[keeper] == msg.sender, "only callable by proposed payee"); + address past = s_keeperInfo[keeper].payee; + s_keeperInfo[keeper].payee = msg.sender; + s_proposedPayee[keeper] = ZERO_ADDRESS; + + emit PayeeshipTransferred(keeper, past, msg.sender); + } + + /** + * @notice signals to keepers that they should not perform upkeeps until the + * contract has been unpaused + */ + function pause() external onlyOwner { + _pause(); + } + + /** + * @notice signals to keepers that they can perform upkeeps once again after + * having been paused + */ + function unpause() external onlyOwner { + _unpause(); + } + + // SETTERS + + /** + * @notice updates the configuration of the registry + * @param paymentPremiumPPB payment premium rate oracles receive on top of + * being reimbursed for gas, measured in parts per billion + * @param flatFeeMicroLink flat fee paid to oracles for performing upkeeps + * @param blockCountPerTurn number of blocks an oracle should wait before + * checking for upkeep + * @param checkGasLimit gas limit when checking for upkeep + * @param stalenessSeconds number of seconds that is allowed for feed data to + * be stale before switching to the fallback pricing + * @param fallbackGasPrice gas price used if the gas price feed is stale + * @param fallbackLinkPrice LINK price used if the LINK price feed is stale + * @param mustTakeTurns flag if true requires node performing Upkeep be different then previous + */ + function setConfig( + uint32 paymentPremiumPPB, + uint32 flatFeeMicroLink, + uint24 blockCountPerTurn, + uint32 checkGasLimit, + uint24 stalenessSeconds, + uint16 gasCeilingMultiplier, + uint256 fallbackGasPrice, + uint256 fallbackLinkPrice, + bool mustTakeTurns + ) public onlyOwner { + s_config = Config({ + paymentPremiumPPB: paymentPremiumPPB, + flatFeeMicroLink: flatFeeMicroLink, + blockCountPerTurn: blockCountPerTurn, + checkGasLimit: checkGasLimit, + stalenessSeconds: stalenessSeconds, + gasCeilingMultiplier: gasCeilingMultiplier, + mustTakeTurns: mustTakeTurns + }); + s_fallbackGasPrice = fallbackGasPrice; + s_fallbackLinkPrice = fallbackLinkPrice; + + emit ConfigSet( + paymentPremiumPPB, + blockCountPerTurn, + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + fallbackGasPrice, + fallbackLinkPrice, + mustTakeTurns + ); + emit FlatFeeSet(flatFeeMicroLink); + } + + /** + * @notice update the list of keepers allowed to perform upkeep + * @param keepers list of addresses allowed to perform upkeep + * @param payees addresses corresponding to keepers who are allowed to + * move payments which have been accrued + */ + function setKeepers(address[] calldata keepers, address[] calldata payees) external onlyOwner { + require(keepers.length == payees.length, "address lists not the same length"); + require(keepers.length >= 2, "not enough keepers"); + for (uint256 i = 0; i < s_keeperList.length; i++) { + address keeper = s_keeperList[i]; + s_keeperInfo[keeper].active = false; + } + for (uint256 i = 0; i < keepers.length; i++) { + address keeper = keepers[i]; + KeeperInfo storage s_keeper = s_keeperInfo[keeper]; + address oldPayee = s_keeper.payee; + address newPayee = payees[i]; + require(newPayee != address(0), "cannot set payee to the zero address"); + require(oldPayee == ZERO_ADDRESS || oldPayee == newPayee || newPayee == IGNORE_ADDRESS, "cannot change payee"); + require(!s_keeper.active, "cannot add keeper twice"); + s_keeper.active = true; + if (newPayee != IGNORE_ADDRESS) { + s_keeper.payee = newPayee; + } + } + s_keeperList = keepers; + emit KeepersUpdated(keepers, payees); + } + + /** + * @notice update registrar + * @param registrar new registrar + */ + function setRegistrar(address registrar) external onlyOwnerOrRegistrar { + address previous = s_registrar; + require(registrar != previous, "Same registrar"); + s_registrar = registrar; + emit RegistrarChanged(previous, registrar); + } + + // GETTERS + + /** + * @notice read all of the details about an upkeep + */ + function getUpkeep(uint256 id) + external + view + override + returns ( + address target, + uint32 executeGas, + bytes memory checkData, + uint96 balance, + address lastKeeper, + address admin, + uint64 maxValidBlocknumber + ) + { + Upkeep memory reg = s_upkeep[id]; + return ( + reg.target, + reg.executeGas, + s_checkData[id], + reg.balance, + reg.lastKeeper, + reg.admin, + reg.maxValidBlocknumber + ); + } + + /** + * @notice read the total number of upkeep's registered + */ + function getUpkeepCount() external view override returns (uint256) { + return s_upkeepCount; + } + + /** + * @notice read the current list canceled upkeep IDs + */ + function getCanceledUpkeepList() external view override returns (uint256[] memory) { + return s_canceledUpkeepList; + } + + /** + * @notice read the current list of addresses allowed to perform upkeep + */ + function getKeeperList() external view override returns (address[] memory) { + return s_keeperList; + } + + /** + * @notice read the current registrar + */ + function getRegistrar() external view returns (address) { + return s_registrar; + } + + /** + * @notice read the current config value for mustTakeTurns + */ + function getMustTakeTurns() external view returns (bool) { + return s_config.mustTakeTurns; + } + + /** + * @notice read the current info about any keeper address + */ + function getKeeperInfo(address query) + external + view + override + returns ( + address payee, + bool active, + uint96 balance + ) + { + KeeperInfo memory keeper = s_keeperInfo[query]; + return (keeper.payee, keeper.active, keeper.balance); + } + + /** + * @notice read the current configuration of the registry + */ + function getConfig() + external + view + override + returns ( + uint32 paymentPremiumPPB, + uint24 blockCountPerTurn, + uint32 checkGasLimit, + uint24 stalenessSeconds, + uint16 gasCeilingMultiplier, + uint256 fallbackGasPrice, + uint256 fallbackLinkPrice + ) + { + Config memory config = s_config; + return ( + config.paymentPremiumPPB, + config.blockCountPerTurn, + config.checkGasLimit, + config.stalenessSeconds, + config.gasCeilingMultiplier, + s_fallbackGasPrice, + s_fallbackLinkPrice + ); + } + + /** + * @notice getFlatFee gets the flat rate fee charged to customers when performing upkeep, + * in units of of micro LINK + */ + function getFlatFee() external view returns (uint32) { + return s_config.flatFeeMicroLink; + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + */ + function getMinBalanceForUpkeep(uint256 id) external view returns (uint96 minBalance) { + return getMaxPaymentForGas(s_upkeep[id].executeGas); + } + + /** + * @notice calculates the maximum payment for a given gas limit + */ + function getMaxPaymentForGas(uint256 gasLimit) public view returns (uint96 maxPayment) { + (uint256 gasWei, uint256 linkEth) = getFeedData(); + uint256 adjustedGasWei = adjustGasPrice(gasWei, false); + return calculatePaymentAmount(gasLimit, adjustedGasWei, linkEth); + } + + // PRIVATE + + /** + * @dev retrieves feed data for fast gas/eth and link/eth prices. if the feed + * data is stale it uses the configured fallback price. Once a price is picked + * for gas it takes the min of gas price in the transaction or the fast gas + * price in order to reduce costs for the upkeep clients. + */ + function getFeedData() private view returns (uint256 gasWei, uint256 linkEth) { + uint32 stalenessSeconds = s_config.stalenessSeconds; + bool staleFallback = stalenessSeconds > 0; + uint256 timestamp; + int256 feedValue; + (, feedValue, , timestamp, ) = FAST_GAS_FEED.latestRoundData(); + if ((staleFallback && stalenessSeconds < block.timestamp - timestamp) || feedValue <= 0) { + gasWei = s_fallbackGasPrice; + } else { + gasWei = uint256(feedValue); + } + (, feedValue, , timestamp, ) = LINK_ETH_FEED.latestRoundData(); + if ((staleFallback && stalenessSeconds < block.timestamp - timestamp) || feedValue <= 0) { + linkEth = s_fallbackLinkPrice; + } else { + linkEth = uint256(feedValue); + } + return (gasWei, linkEth); + } + + /** + * @dev calculates LINK paid for gas spent plus a configure premium percentage + */ + function calculatePaymentAmount( + uint256 gasLimit, + uint256 gasWei, + uint256 linkEth + ) private view returns (uint96 payment) { + Config memory config = s_config; + uint256 weiForGas = gasWei.mul(gasLimit.add(REGISTRY_GAS_OVERHEAD)); + uint256 premium = PPB_BASE.add(config.paymentPremiumPPB); + uint256 total = weiForGas.mul(1e9).mul(premium).div(linkEth).add(uint256(config.flatFeeMicroLink).mul(1e12)); + require(total <= LINK_TOTAL_SUPPLY, "payment greater than all LINK"); + return uint96(total); + // LINK_TOTAL_SUPPLY < UINT96_MAX + } + + /** + * @dev calls target address with exactly gasAmount gas and data as calldata + * or reverts if at least gasAmount gas is not available + */ + function callWithExactGas( + uint256 gasAmount, + address target, + bytes memory data + ) private returns (bool success) { + assembly { + let g := gas() + // Compute g -= CUSHION and check for underflow + if lt(g, CUSHION) { + revert(0, 0) + } + g := sub(g, CUSHION) + // if g - g//64 <= gasAmount, revert + // (we subtract g//64 because of EIP-150) + if iszero(gt(sub(g, div(g, 64)), gasAmount)) { + revert(0, 0) + } + // solidity calls check that a contract actually exists at the destination, so we do the same + if iszero(extcodesize(target)) { + revert(0, 0) + } + // call and return whether we succeeded. ignore return data + success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) + } + return success; + } + + /** + * @dev calls the Upkeep target with the performData param passed in by the + * keeper and the exact gas required by the Upkeep + */ + function performUpkeepWithParams(PerformParams memory params) + private + nonReentrant + validUpkeep(params.id) + returns (bool success) + { + Upkeep memory upkeep = s_upkeep[params.id]; + prePerformUpkeep(upkeep, params.from, params.maxLinkPayment); + + uint256 gasUsed = gasleft(); + bytes memory callData = abi.encodeWithSelector(PERFORM_SELECTOR, params.performData); + success = callWithExactGas(params.gasLimit, upkeep.target, callData); + gasUsed = gasUsed - gasleft(); + + uint96 payment = calculatePaymentAmount(gasUsed, params.adjustedGasWei, params.linkEth); + upkeep.balance = upkeep.balance.sub(payment); + upkeep.lastKeeper = params.from; + s_upkeep[params.id] = upkeep; + uint96 newBalance = s_keeperInfo[params.from].balance.add(payment); + s_keeperInfo[params.from].balance = newBalance; + + emit UpkeepPerformed(params.id, success, params.from, payment, params.performData); + return success; + } + + /** + * @dev ensures a upkeep is valid + */ + function validateUpkeep(uint256 id) private view { + require(s_upkeep[id].maxValidBlocknumber > block.number, "invalid upkeep id"); + } + + /** + * @dev ensures all required checks are passed before an upkeep is performed + */ + function prePerformUpkeep( + Upkeep memory upkeep, + address from, + uint256 maxLinkPayment + ) private view { + require(s_keeperInfo[from].active, "only active keepers"); + require(upkeep.balance >= maxLinkPayment, "insufficient funds"); + if (s_config.mustTakeTurns) { + require(upkeep.lastKeeper != from, "keepers must take turns"); + } + } + + /** + * @dev adjusts the gas price to min(ceiling, tx.gasprice) or just uses the ceiling if tx.gasprice is disabled + */ + function adjustGasPrice(uint256 gasWei, bool useTxGasPrice) private view returns (uint256 adjustedPrice) { + adjustedPrice = gasWei.mul(s_config.gasCeilingMultiplier); + if (useTxGasPrice && tx.gasprice < adjustedPrice) { + adjustedPrice = tx.gasprice; + } + } + + /** + * @dev generates a PerformParams struct for use in performUpkeepWithParams() + */ + function generatePerformParams( + address from, + uint256 id, + bytes memory performData, + bool useTxGasPrice + ) private view returns (PerformParams memory) { + uint256 gasLimit = s_upkeep[id].executeGas; + (uint256 gasWei, uint256 linkEth) = getFeedData(); + uint256 adjustedGasWei = adjustGasPrice(gasWei, useTxGasPrice); + uint96 maxLinkPayment = calculatePaymentAmount(gasLimit, adjustedGasWei, linkEth); + + return + PerformParams({ + from: from, + id: id, + performData: performData, + maxLinkPayment: maxLinkPayment, + gasLimit: gasLimit, + adjustedGasWei: adjustedGasWei, + linkEth: linkEth + }); + } + + /** + * @dev extracts a revert reason from a call result payload + */ + function getRevertMsg(bytes memory _payload) private pure returns (string memory) { + if (_payload.length < 68) return "transaction reverted silently"; + assembly { + _payload := add(_payload, 0x04) + } + return abi.decode(_payload, (string)); + } + + // MODIFIERS + + /** + * @dev ensures a upkeep is valid + */ + modifier validUpkeep(uint256 id) { + validateUpkeep(id); + _; + } + + /** + * @dev ensures that burns don't accidentally happen by sending to the zero + * address + */ + modifier validateRecipient(address to) { + require(to != address(0), "cannot send to zero address"); + _; + } + + /** + * @dev Reverts if called by anyone other than the contract owner or registrar. + */ + modifier onlyOwnerOrRegistrar() { + require(msg.sender == owner() || msg.sender == s_registrar, "Only callable by owner or registrar"); + _; + } +} diff --git a/contracts/src/v0.7/UpkeepRegistrationRequests.sol b/contracts/src/v0.7/UpkeepRegistrationRequests.sol index 599bc75117c..9c0fe6efc81 100644 --- a/contracts/src/v0.7/UpkeepRegistrationRequests.sol +++ b/contracts/src/v0.7/UpkeepRegistrationRequests.sol @@ -3,6 +3,7 @@ pragma solidity ^0.7.0; import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/KeeperRegistryInterface.sol"; +import "./interfaces/TypeAndVersionInterface.sol"; import "./vendor/SafeMath96.sol"; import "./ConfirmedOwner.sol"; @@ -16,7 +17,7 @@ import "./ConfirmedOwner.sol"; * The idea is to have same interface(functions,events) for UI or anyone using this contract irrespective of auto approve being enabled or not. * they can just listen to `RegistrationRequested` & `RegistrationApproved` events and know the status on registrations. */ -contract UpkeepRegistrationRequests is ConfirmedOwner { +contract UpkeepRegistrationRequests is TypeAndVersionInterface, ConfirmedOwner { using SafeMath96 for uint96; bytes4 private constant REGISTER_REQUEST_SELECTOR = this.register.selector; @@ -26,6 +27,12 @@ contract UpkeepRegistrationRequests is ConfirmedOwner { LinkTokenInterface public immutable LINK; + /** + * @notice versions: + * - UpkeepRegistration 1.0.0: initial release + */ + string public constant override typeAndVersion = "UpkeepRegistrationRequests 1.0.0"; + struct AutoApprovedConfig { bool enabled; uint16 allowedPerWindow; diff --git a/contracts/src/v0.7/tests/UpkeepCounter.sol b/contracts/src/v0.7/tests/UpkeepCounter.sol index 4b900437443..3c42b58255f 100644 --- a/contracts/src/v0.7/tests/UpkeepCounter.sol +++ b/contracts/src/v0.7/tests/UpkeepCounter.sol @@ -1,17 +1,25 @@ pragma solidity ^0.7.6; contract UpkeepCounter { - event PerformingUpkeep(address from, uint256 initialBlock, uint256 lastBlock, uint256 counter); + event PerformingUpkeep( + address indexed from, + uint256 initialBlock, + uint256 lastBlock, + uint256 previousBlock, + uint256 counter + ); uint256 public testRange; uint256 public interval; uint256 public lastBlock; + uint256 public previousPerformBlock; uint256 public initialBlock; uint256 public counter; constructor(uint256 _testRange, uint256 _interval) { testRange = _testRange; interval = _interval; + previousPerformBlock = 0; lastBlock = block.number; initialBlock = 0; counter = 0; @@ -28,7 +36,8 @@ contract UpkeepCounter { lastBlock = block.number; counter = counter + 1; performData; - emit PerformingUpkeep(tx.origin, initialBlock, lastBlock, counter); + emit PerformingUpkeep(tx.origin, initialBlock, lastBlock, previousPerformBlock, counter); + previousPerformBlock = lastBlock; } function eligible() public view returns (bool) { diff --git a/contracts/src/v0.8/dev/ArbitrumSequencerUptimeFeed.sol b/contracts/src/v0.8/dev/ArbitrumSequencerUptimeFeed.sol index a8205c8cf17..76d3600d81c 100644 --- a/contracts/src/v0.8/dev/ArbitrumSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/dev/ArbitrumSequencerUptimeFeed.sol @@ -53,7 +53,7 @@ contract ArbitrumSequencerUptimeFeed is /// @dev Follows: https://eips.ethereum.org/EIPS/eip-1967 address public constant FLAG_L2_SEQ_OFFLINE = - address(bytes20(bytes32(uint256(keccak256("chainlink.flags.l2-seq-offline")) - 1))); + address(bytes20(bytes32(uint256(keccak256("chainlink.flags.arbitrum-seq-offline")) - 1))); uint8 public constant override decimals = 0; string public constant override description = "L2 Sequencer Uptime Status Feed"; diff --git a/contracts/src/v0.8/dev/BatchBlockhashStore.sol b/contracts/src/v0.8/dev/BatchBlockhashStore.sol new file mode 100644 index 00000000000..ca0c29ca03d --- /dev/null +++ b/contracts/src/v0.8/dev/BatchBlockhashStore.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.6; + +/** + * @title BatchBlockhashStore + * @notice The BatchBlockhashStore contract acts as a proxy to write many blockhashes to the + * provided BlockhashStore contract efficiently in a single transaction. This results + * in plenty of gas savings and higher throughput of blockhash storage, which is desirable + * in times of high network congestion. + */ +contract BatchBlockhashStore { + BlockhashStore public immutable BHS; + + constructor(address blockhashStoreAddr) { + BHS = BlockhashStore(blockhashStoreAddr); + } + + /** + * @notice stores blockhashes of the given block numbers in the configured blockhash store, assuming + * they are availble though the blockhash() instruction. + * @param blockNumbers the block numbers to store the blockhashes of. Must be available via the + * blockhash() instruction, otherwise this function call will revert. + */ + function store(uint256[] memory blockNumbers) public { + for (uint256 i = 0; i < blockNumbers.length; i++) { + // skip the block if it's not storeable, the caller will have to check + // after the transaction is mined to see if the blockhash was truly stored. + if (!storeableBlock(blockNumbers[i])) { + continue; + } + BHS.store(blockNumbers[i]); + } + } + + /** + * @notice stores blockhashes after verifying blockheader of child/subsequent block + * @param blockNumbers the block numbers whose blockhashes should be stored, in decreasing order + * @param headers the rlp-encoded block headers of blockNumbers[i] + 1. + */ + function storeVerifyHeader(uint256[] memory blockNumbers, bytes[] memory headers) public { + require(blockNumbers.length == headers.length, "input array arg lengths mismatch"); + for (uint256 i = 0; i < blockNumbers.length; i++) { + BHS.storeVerifyHeader(blockNumbers[i], headers[i]); + } + } + + /** + * @notice retrieves blockhashes of all the given block numbers from the blockhash store, if available. + * @param blockNumbers array of block numbers to fetch blockhashes for + * @return blockhashes array of block hashes corresponding to each block number provided in the `blockNumbers` + * param. If the blockhash is not found, 0x0 is returned instead of the real blockhash, indicating + * that it is not in the blockhash store. + */ + function getBlockhashes(uint256[] memory blockNumbers) external view returns (bytes32[] memory) { + bytes32[] memory blockHashes = new bytes32[](blockNumbers.length); + for (uint256 i = 0; i < blockNumbers.length; i++) { + try BHS.getBlockhash(blockNumbers[i]) returns (bytes32 bh) { + blockHashes[i] = bh; + } catch Error( + string memory /* reason */ + ) { + blockHashes[i] = 0x0; + } + } + return blockHashes; + } + + /** + * @notice returns true if and only if the given block number's blockhash can be retrieved + * using the blockhash() instruction. + * @param blockNumber the block number to check if it's storeable with blockhash() + */ + function storeableBlock(uint256 blockNumber) private view returns (bool) { + // handle edge case on simulated chains which possibly have < 256 blocks total. + return block.number <= 256 ? true : blockNumber >= (block.number - 256); + } +} + +interface BlockhashStore { + function storeVerifyHeader(uint256 n, bytes memory header) external; + + function store(uint256 n) external; + + function getBlockhash(uint256 n) external view returns (bytes32); +} diff --git a/contracts/src/v0.7/tests/DerivedPriceFeed.sol b/contracts/src/v0.8/dev/DerivedPriceFeed.sol similarity index 55% rename from contracts/src/v0.7/tests/DerivedPriceFeed.sol rename to contracts/src/v0.8/dev/DerivedPriceFeed.sol index 06bf81f3d78..4c079632f81 100644 --- a/contracts/src/v0.7/tests/DerivedPriceFeed.sol +++ b/contracts/src/v0.8/dev/DerivedPriceFeed.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; +pragma solidity ^0.8.6; -import "../interfaces/AggregatorV2V3Interface.sol"; +import "../interfaces/AggregatorV3Interface.sol"; /** * Network: Fantom Testnet @@ -18,25 +18,22 @@ import "../interfaces/AggregatorV2V3Interface.sol"; * Quote Address: 0x5498BB86BC934c8D34FDA08E81D444153d0D06aD * Decimals: 18 * - * Chainlink Data Feeds can be used in combination to derive denominated price pairs in other currencies. - * If you require a denomination other than what is provided, you can use two data feeds to derive the pair that you need. - * For example, if you needed a LINK / FTM price, you could take the LINK / USD feed and the FTM / USD feed and derive LINK / FTM using division. + * Chainlink Data Feeds can be used in combination to derive denominated price pairs in other + * currencies. + * + * If you require a denomination other than what is provided, you can use two data feeds to derive + * the pair that you need. + * + * For example, if you needed a LINK / FTM price, you could take the LINK / USD feed and the + * FTM / USD feed and derive LINK / FTM using division. * (LINK/USD)/(FTM/USD) = LINK/FTM */ -contract DerivedPriceFeed is AggregatorV2V3Interface { +contract DerivedPriceFeed is AggregatorV3Interface { uint256 public constant override version = 0; - uint8 public override decimals; - int256 public override latestAnswer; - uint256 public override latestTimestamp; - uint256 public override latestRound; - - mapping(uint256 => int256) public override getAnswer; - mapping(uint256 => uint256) public override getTimestamp; - mapping(uint256 => uint256) private getStartedAt; - - AggregatorV3Interface public base; - AggregatorV3Interface public quote; + AggregatorV3Interface public immutable BASE; + AggregatorV3Interface public immutable QUOTE; + uint8 public immutable DECIMALS; constructor( address _base, @@ -44,9 +41,13 @@ contract DerivedPriceFeed is AggregatorV2V3Interface { uint8 _decimals ) { require(_decimals > uint8(0) && _decimals <= uint8(18), "Invalid _decimals"); - decimals = _decimals; - base = AggregatorV3Interface(_base); - quote = AggregatorV3Interface(_quote); + DECIMALS = _decimals; + BASE = AggregatorV3Interface(_base); + QUOTE = AggregatorV3Interface(_quote); + } + + function decimals() external view override returns (uint8) { + return DECIMALS; } function getRoundData(uint80) @@ -80,25 +81,20 @@ contract DerivedPriceFeed is AggregatorV2V3Interface { uint80 answeredInRound ) { - return (uint80(0), getDerivedPrice(base, quote, decimals), block.timestamp, block.timestamp, uint80(0)); + return (uint80(0), getDerivedPrice(), block.timestamp, block.timestamp, uint80(0)); } // https://docs.chain.link/docs/get-the-latest-price/#getting-a-different-price-denomination - function getDerivedPrice( - AggregatorV3Interface _base, - AggregatorV3Interface _quote, - uint8 _decimals - ) internal view returns (int256) { - int256 decimals = int256(10**uint256(_decimals)); - (, int256 basePrice, , , ) = _base.latestRoundData(); - uint8 baseDecimals = _base.decimals(); - basePrice = scalePrice(basePrice, baseDecimals, _decimals); + function getDerivedPrice() internal view returns (int256) { + (, int256 basePrice, , , ) = BASE.latestRoundData(); + uint8 baseDecimals = BASE.decimals(); + basePrice = scalePrice(basePrice, baseDecimals, DECIMALS); - (, int256 quotePrice, , , ) = _quote.latestRoundData(); - uint8 quoteDecimals = _quote.decimals(); - quotePrice = scalePrice(quotePrice, quoteDecimals, _decimals); + (, int256 quotePrice, , , ) = QUOTE.latestRoundData(); + uint8 quoteDecimals = QUOTE.decimals(); + quotePrice = scalePrice(quotePrice, quoteDecimals, DECIMALS); - return (basePrice * decimals) / quotePrice; + return (basePrice * int256(10**uint256(DECIMALS))) / quotePrice; } function scalePrice( diff --git a/contracts/src/v0.8/tests/VRFLoadTestExternalSubOwner.sol b/contracts/src/v0.8/tests/VRFLoadTestExternalSubOwner.sol new file mode 100644 index 00000000000..ee60b37f641 --- /dev/null +++ b/contracts/src/v0.8/tests/VRFLoadTestExternalSubOwner.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../interfaces/LinkTokenInterface.sol"; +import "../interfaces/VRFCoordinatorV2Interface.sol"; +import "../VRFConsumerBaseV2.sol"; +import "../ConfirmedOwner.sol"; + +/** + * @title The VRFLoadTestExternalSubOwner contract. + * @notice Allows making many VRF V2 randomness requests in a single transaction for load testing. + */ +contract VRFLoadTestExternalSubOwner is VRFConsumerBaseV2, ConfirmedOwner { + VRFCoordinatorV2Interface public immutable COORDINATOR; + LinkTokenInterface public immutable LINK; + + uint256 public s_responseCount; + + constructor(address _vrfCoordinator, address _link) VRFConsumerBaseV2(_vrfCoordinator) ConfirmedOwner(msg.sender) { + COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator); + LINK = LinkTokenInterface(_link); + } + + function fulfillRandomWords(uint256, uint256[] memory) internal override { + s_responseCount++; + } + + function requestRandomWords( + uint64 _subId, + uint16 _requestConfirmations, + bytes32 _keyHash, + uint16 _requestCount + ) external onlyOwner { + for (uint16 i = 0; i < _requestCount; i++) { + COORDINATOR.requestRandomWords(_keyHash, _subId, _requestConfirmations, 50_000, 1); + } + } +} diff --git a/contracts/src/v0.8/tests/VRFLoadTestOwnerlessConsumer.sol b/contracts/src/v0.8/tests/VRFLoadTestOwnerlessConsumer.sol new file mode 100644 index 00000000000..47cd824bebd --- /dev/null +++ b/contracts/src/v0.8/tests/VRFLoadTestOwnerlessConsumer.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../VRFConsumerBase.sol"; +import "../interfaces/ERC677ReceiverInterface.sol"; + +/** + * @title The VRFLoadTestOwnerlessConsumer contract. + * @notice Allows making many VRF V1 randomness requests in a single transaction for load testing. + */ +contract VRFLoadTestOwnerlessConsumer is VRFConsumerBase, ERC677ReceiverInterface { + // The price of each VRF request in Juels. 1 LINK = 1e18 Juels. + uint256 public immutable PRICE; + + uint256 public s_responseCount; + + constructor( + address _vrfCoordinator, + address _link, + uint256 _price + ) VRFConsumerBase(_vrfCoordinator, _link) { + PRICE = _price; + } + + function fulfillRandomness(bytes32, uint256) internal override { + s_responseCount++; + } + + /** + * @dev Creates as many randomness requests as can be made with the funds transferred. + * @param _amount The amount of LINK transferred to pay for these requests. + * @param _data The data passed to transferAndCall on LinkToken. Must be an abi-encoded key hash. + */ + function onTokenTransfer( + address, + uint256 _amount, + bytes calldata _data + ) external override { + if (msg.sender != address(LINK)) { + revert("only callable from LINK"); + } + bytes32 keyHash = abi.decode(_data, (bytes32)); + + uint256 spent = 0; + while (spent + PRICE <= _amount) { + requestRandomness(keyHash, PRICE); + spent += PRICE; + } + } +} diff --git a/contracts/src/v0.8/upkeeps/CronUpkeep.sol b/contracts/src/v0.8/upkeeps/CronUpkeep.sol index 4f4dc65800d..eea1ad5853b 100644 --- a/contracts/src/v0.8/upkeeps/CronUpkeep.sol +++ b/contracts/src/v0.8/upkeeps/CronUpkeep.sol @@ -223,6 +223,7 @@ contract CronUpkeep is KeeperCompatibleInterface, KeeperBase, ConfirmedOwner, Pa address target, bytes memory handler ) private { + tickTime = tickTime - (tickTime % 60); // remove seconds from tick time if (block.timestamp < tickTime) { revert TickInFuture(); } diff --git a/contracts/test/v0.7/KeeperRegistry.test.ts b/contracts/test/v0.7/KeeperRegistry.test.ts index efc96e65e3a..8c39956f2f0 100644 --- a/contracts/test/v0.7/KeeperRegistry.test.ts +++ b/contracts/test/v0.7/KeeperRegistry.test.ts @@ -168,6 +168,13 @@ describe('KeeperRegistry', () => { return base.add(premium).add(flatFeeJules) } + describe('#typeAndVersion', () => { + it('uses the correct type and version', async () => { + const typeAndVersion = await registry.typeAndVersion() + assert.equal(typeAndVersion, 'KeeperRegistry 1.1.0') + }) + }) + describe('#setKeepers', () => { const IGNORE_ADDRESS = '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF' it('reverts when not called by the owner', async () => { diff --git a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts b/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts index 94254d2a8f4..0009473a3b2 100644 --- a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts +++ b/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts @@ -121,6 +121,13 @@ describe('UpkeepRegistrationRequests', () => { await registry.setRegistrar(registrar.address) }) + describe('#typeAndVersion', () => { + it('uses the correct type and version', async () => { + const typeAndVersion = await registrar.typeAndVersion() + assert.equal(typeAndVersion, 'UpkeepRegistrationRequests 1.0.0') + }) + }) + describe('#register', () => { it('reverts if not called by the LINK token', async () => { await evmRevert( diff --git a/contracts/test/v0.8/CronUpkeep.test.ts b/contracts/test/v0.8/CronUpkeep.test.ts index 25ec3e58b01..f25eb396a0b 100644 --- a/contracts/test/v0.8/CronUpkeep.test.ts +++ b/contracts/test/v0.8/CronUpkeep.test.ts @@ -262,6 +262,29 @@ describe('CronUpkeep', () => { assert.isTrue(needsUpkeep) await cron.connect(stranger).performUpkeep(payload) }) + + it('is only callable once for a given tick', async () => { + await h.fastForward(moment.duration(10, 'minutes').asSeconds()) + const [needsUpkeep, payload] = await cron + .connect(AddressZero) + .callStatic.checkUpkeep('0x') + assert.isTrue(needsUpkeep) + const maliciousPayload = encodePayload([ + 2, + moment.unix(timeStamp).add(10, 'minutes').add(59, 'seconds').unix(), + cronReceiver1.address, + handler2Sig, + ]) + await cron.performUpkeep(payload) + await expect(cron.performUpkeep(payload)).to.be.reverted + await expect(cron.performUpkeep(maliciousPayload)).to.be.reverted + await h.fastForward(moment.duration(1, 'minute').asSeconds()) + await expect(cron.performUpkeep(payload)).to.be.reverted + await expect(cron.performUpkeep(maliciousPayload)).to.be.reverted + await h.fastForward(moment.duration(10, 'minute').asSeconds()) + await expect(cron.performUpkeep(payload)).to.be.reverted + await expect(cron.performUpkeep(maliciousPayload)).to.be.reverted + }) }) }) diff --git a/contracts/test/v0.8/dev/ArbitrumSequencerUptimeFeed.test.ts b/contracts/test/v0.8/dev/ArbitrumSequencerUptimeFeed.test.ts index 16edac9b201..e46da8171bf 100644 --- a/contracts/test/v0.8/dev/ArbitrumSequencerUptimeFeed.test.ts +++ b/contracts/test/v0.8/dev/ArbitrumSequencerUptimeFeed.test.ts @@ -1,5 +1,5 @@ import { ethers, network } from 'hardhat' -import { Contract, BigNumber } from 'ethers' +import { BigNumber, Contract } from 'ethers' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -11,6 +11,8 @@ describe('ArbitrumSequencerUptimeFeed', () => { let deployer: SignerWithAddress let l1Owner: SignerWithAddress let l2Messenger: SignerWithAddress + const gasUsedDeviation = 100 + before(async () => { const accounts = await ethers.getSigners() deployer = accounts[0] @@ -84,6 +86,16 @@ describe('ArbitrumSequencerUptimeFeed', () => { ) }) + describe('constants', () => { + it('should have the correct value for FLAG_L2_SEQ_OFFLINE', async () => { + const flag: string = + await arbitrumSequencerUptimeFeed.FLAG_L2_SEQ_OFFLINE() + expect(flag.toLowerCase()).to.equal( + '0xa438451d6458044c3c8cd2f6f31c91ac882a6d91', + ) + }) + }) + describe('#updateStatus', () => { it(`should update status when status has changed and incoming timestamp is newer than the latest`, async () => { let timestamp = await arbitrumSequencerUptimeFeed.latestTimestamp() @@ -280,7 +292,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { const noUpdateTx = await _noUpdateTx.wait(1) // Assert no update expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(0) - expect(noUpdateTx.cumulativeGasUsed).to.equal(28300) + expect(noUpdateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 28300, + gasUsedDeviation, + ) // Gas for update timestamp = timestamp.add(1000) @@ -290,7 +305,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { const updateTx = await _updateTx.wait(1) // Assert update expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(1) - expect(updateTx.cumulativeGasUsed).to.equal(93137) + expect(updateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 93137, + gasUsedDeviation, + ) }) describe('Aggregator interface', () => { @@ -311,7 +329,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.getRoundData(1), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(31157) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 31157, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for latestRoundData() @skip-coverage', async () => { @@ -321,7 +342,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.latestRoundData(), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(28523) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 28523, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for latestAnswer() @skip-coverage', async () => { @@ -331,7 +355,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.latestAnswer(), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(28329) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 28329, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for latestTimestamp() @skip-coverage', async () => { @@ -341,7 +368,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.latestTimestamp(), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(28229) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 28229, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for latestRound() @skip-coverage', async () => { @@ -351,7 +381,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.latestRound(), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(28245) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 28245, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for getAnswer(roundId) @skip-coverage', async () => { @@ -361,7 +394,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.getAnswer(1), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(30799) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 30799, + gasUsedDeviation, + ) }) it('should consume a known amount of gas for getTimestamp(roundId) @skip-coverage', async () => { @@ -371,7 +407,10 @@ describe('ArbitrumSequencerUptimeFeed', () => { .populateTransaction.getTimestamp(1), ) const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed).to.equal(30753) + expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( + 30753, + gasUsedDeviation, + ) }) }) }) diff --git a/contracts/test/v0.8/dev/BatchBlockhashStore.test.ts b/contracts/test/v0.8/dev/BatchBlockhashStore.test.ts new file mode 100644 index 00000000000..5d7dadf1e9c --- /dev/null +++ b/contracts/test/v0.8/dev/BatchBlockhashStore.test.ts @@ -0,0 +1,324 @@ +import { assert, expect } from 'chai' +import { Contract, Signer } from 'ethers' +import { ethers } from 'hardhat' +import * as rlp from 'rlp' + +function range(size: number, startAt = 0) { + return [...Array(size).keys()].map((i) => i + startAt) +} + +describe('BatchBlockhashStore', () => { + let blockhashStore: Contract + let batchBHS: Contract + let owner: Signer + + beforeEach(async () => { + const accounts = await ethers.getSigners() + owner = accounts[0] + + const bhFactory = await ethers.getContractFactory( + 'src/v0.6/dev/BlockhashStore.sol:BlockhashStore', + accounts[0], + ) + + blockhashStore = await bhFactory.deploy() + + const batchBHSFactory = await ethers.getContractFactory( + 'src/v0.8/dev/BatchBlockhashStore.sol:BatchBlockhashStore', + accounts[0], + ) + + batchBHS = await batchBHSFactory.deploy(blockhashStore.address) + + // Mine some blocks so that we have some blockhashes to store. + for (let i = 0; i < 10; i++) { + await ethers.provider.send('evm_mine', []) + } + }) + + describe('#store', () => { + it('stores batches of blocknumbers', async () => { + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + const bottomBlock = latestBlock - 5 + const numBlocks = 3 + await batchBHS.connect(owner).store(range(numBlocks, bottomBlock)) + + // Mine some blocks to confirm the store batch tx above. + for (let i = 0; i < 2; i++) { + await ethers.provider.send('evm_mine', []) + } + + // check the bhs if it was stored + for (let i = bottomBlock; i < bottomBlock + numBlocks; i++) { + const actualBh = await blockhashStore.connect(owner).getBlockhash(i) + const expectedBh = (await ethers.provider.getBlock(i)).hash + expect(expectedBh).to.equal(actualBh) + } + }) + + it('skips block numbers that are too far back', async () => { + // blockhash(n) fails if n is more than 256 blocks behind the current block in which + // the instruction is executing. + for (let i = 0; i < 256; i++) { + await ethers.provider.send('evm_mine', []) + } + + const gettableBlock = + (await ethers.provider.send('eth_blockNumber', [])) - 1 + + // Store 3 block numbers that are too far back, and one that is close enough. + await batchBHS.connect(owner).store([1, 2, 3, gettableBlock]) + + await ethers.provider.send('evm_mine', []) + + // Only block "250" should be stored + const actualBh = await blockhashStore + .connect(owner) + .getBlockhash(gettableBlock) + const expectedBh = (await ethers.provider.getBlock(gettableBlock)).hash + expect(expectedBh).to.equal(actualBh) + + // others were not stored + for (let i of [1, 2, 3]) { + expect( + blockhashStore.connect(owner).getBlockhash(i), + ).to.be.revertedWith('blockhash not found in store') + } + }) + }) + + describe('#getBlockhashes', () => { + it('fetches blockhashes of a batch of block numbers', async () => { + // Store a bunch of block hashes + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + const bottomBlock = latestBlock - 5 + const numBlocks = 3 + await batchBHS.connect(owner).store(range(numBlocks, bottomBlock)) + + // Mine some blocks to confirm the store batch tx above. + for (let i = 0; i < 2; i++) { + await ethers.provider.send('evm_mine', []) + } + + // fetch the blocks in a batch + const actualBlockhashes = await batchBHS + .connect(owner) + .getBlockhashes(range(numBlocks, bottomBlock)) + let expectedBlockhashes = [] + for (let i = bottomBlock; i < bottomBlock + numBlocks; i++) { + const block = await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + i.toString(16), + false, + ]) + expectedBlockhashes.push(block.hash) + } + assert.deepEqual(actualBlockhashes, expectedBlockhashes) + }) + + it('returns 0x0 for block numbers without an associated blockhash', async () => { + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + const bottomBlock = latestBlock - 5 + const numBlocks = 3 + const blockhashes = await batchBHS + .connect(owner) + .getBlockhashes(range(numBlocks, bottomBlock)) + const expected = [ + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ] + assert.deepEqual(blockhashes, expected) + }) + }) + + describe('#storeVerifyHeader', () => { + it('stores batches of blocknumbers using storeVerifyHeader', async () => { + // Store a single blockhash and go backwards from there using storeVerifyHeader + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + await batchBHS.connect(owner).store([latestBlock]) + + await ethers.provider.send('evm_mine', []) + + const numBlocks = 3 + const startBlock = latestBlock - 1 + const blockNumbers = range( + numBlocks + 1, + startBlock - numBlocks, + ).reverse() + let blockHeaders = [] + let expectedBlockhashes = [] + for (let i of blockNumbers) { + const block = await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + (i + 1).toString(16), + false, + ]) + // eip 1559 header - switch to this if we upgrade hardhat + // and use post-london forks of ethereum. + // const encodedHeader = rlp.encode([ + // block.parentHash, + // block.sha3Uncles, + // ethers.utils.arrayify(block.miner), + // block.stateRoot, + // block.transactionsRoot, + // block.receiptsRoot, + // block.logsBloom, + // block.difficulty, + // block.number, + // block.gasLimit, + // block.gasUsed == '0x0' ? '0x' : block.gasUsed, + // block.timestamp, + // block.extraData, + // block.mixHash, + // block.nonce, + // block.baseFeePerGas, + // ]) + // pre-london block header serialization + const encodedHeader = rlp.encode([ + block.parentHash, + block.sha3Uncles, + ethers.utils.arrayify(block.miner), + block.stateRoot, + block.transactionsRoot, + block.receiptsRoot, + block.logsBloom, + block.difficulty, + block.number, + block.gasLimit, + block.gasUsed == '0x0' ? '0x' : block.gasUsed, + block.timestamp, + block.extraData, + block.mixHash, + block.nonce, + ]) + blockHeaders.push('0x' + encodedHeader.toString('hex')) + expectedBlockhashes.push( + ( + await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + i.toString(16), + false, + ]) + ).hash, + ) + } + await batchBHS + .connect(owner) + .storeVerifyHeader(blockNumbers, blockHeaders) + + // fetch blocks that were just stored and assert correctness + const actualBlockhashes = await batchBHS + .connect(owner) + .getBlockhashes(blockNumbers) + assert.deepEqual(actualBlockhashes, expectedBlockhashes) + }) + + describe('bad input', () => { + it('reverts on mismatched input array sizes', async () => { + // Store a single blockhash and go backwards from there using storeVerifyHeader + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + await batchBHS.connect(owner).store([latestBlock]) + + await ethers.provider.send('evm_mine', []) + + const numBlocks = 3 + const startBlock = latestBlock - 1 + const blockNumbers = range( + numBlocks + 1, + startBlock - numBlocks, + ).reverse() + let blockHeaders = [] + let expectedBlockhashes = [] + for (let i of blockNumbers) { + const block = await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + (i + 1).toString(16), + false, + ]) + const encodedHeader = rlp.encode([ + block.parentHash, + block.sha3Uncles, + ethers.utils.arrayify(block.miner), + block.stateRoot, + block.transactionsRoot, + block.receiptsRoot, + block.logsBloom, + block.difficulty, + block.number, + block.gasLimit, + block.gasUsed == '0x0' ? '0x' : block.gasUsed, + block.timestamp, + block.extraData, + block.mixHash, + block.nonce, + block.baseFeePerGas, + ]) + blockHeaders.push('0x' + encodedHeader.toString('hex')) + expectedBlockhashes.push( + ( + await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + i.toString(16), + false, + ]) + ).hash, + ) + } + // remove last element to simulate different input array sizes + blockHeaders.pop() + expect( + batchBHS.connect(owner).storeVerifyHeader(blockNumbers, blockHeaders), + ).to.be.revertedWith('input array arg lengths mismatch') + }) + + it('reverts on bad block header input', async () => { + // Store a single blockhash and go backwards from there using storeVerifyHeader + const latestBlock = await ethers.provider.send('eth_blockNumber', []) + await batchBHS.connect(owner).store([latestBlock]) + + await ethers.provider.send('evm_mine', []) + + const numBlocks = 3 + const startBlock = latestBlock - 1 + const blockNumbers = range( + numBlocks + 1, + startBlock - numBlocks, + ).reverse() + let blockHeaders = [] + let expectedBlockhashes = [] + for (let i of blockNumbers) { + const block = await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + (i + 1).toString(16), + false, + ]) + const encodedHeader = rlp.encode([ + block.parentHash, + block.sha3Uncles, + ethers.utils.arrayify(block.miner), + block.stateRoot, + block.transactionsRoot, + block.receiptsRoot, + block.logsBloom, + block.difficulty, + block.number, + block.gasLimit, + block.gasUsed, // incorrect: in cases where it's 0x0 it should be 0x instead. + block.timestamp, + block.extraData, + block.mixHash, + block.nonce, + block.baseFeePerGas, + ]) + blockHeaders.push('0x' + encodedHeader.toString('hex')) + expectedBlockhashes.push( + ( + await ethers.provider.send('eth_getBlockByNumber', [ + '0x' + i.toString(16), + false, + ]) + ).hash, + ) + } + expect( + batchBHS.connect(owner).storeVerifyHeader(blockNumbers, blockHeaders), + ).to.be.revertedWith('header has unknown blockhash') + }) + }) + }) +}) diff --git a/contracts/test/v0.8/dev/DerivedPriceFeed.test.ts b/contracts/test/v0.8/dev/DerivedPriceFeed.test.ts new file mode 100644 index 00000000000..5171a44d1f5 --- /dev/null +++ b/contracts/test/v0.8/dev/DerivedPriceFeed.test.ts @@ -0,0 +1,141 @@ +import { ethers } from 'hardhat' +import { BigNumber, ContractFactory } from 'ethers' +import { expect } from 'chai' +import { describe } from 'mocha' + +describe('DerivedPriceFeed', () => { + let mockAggFactory: ContractFactory + let derivedFeedFactory: ContractFactory + before(async () => { + const accounts = await ethers.getSigners() + mockAggFactory = await ethers.getContractFactory( + 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', + accounts[0], + ) + derivedFeedFactory = await ethers.getContractFactory( + 'src/v0.8/dev/DerivedPriceFeed.sol:DerivedPriceFeed', + accounts[0], + ) + }) + + it('reverts on getRoundData', async () => { + let base = await mockAggFactory.deploy(8, 10e8) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 8, + ) + + await expect(derived.getRoundData(1)).to.be.reverted + }) + + it('returns decimals', async () => { + let base = await mockAggFactory.deploy(8, 10e8) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 9, + ) + + await expect(await derived.decimals()).to.equal(9) + }) + + describe('calculates price', async () => { + it('when all decimals are the same', async () => { + let base = await mockAggFactory.deploy(8, 10e8) // 10 + let quote = await mockAggFactory.deploy(8, 5e8) // 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 8, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 2e8 /* 2 */, + ) + }) + + it('when all decimals are the same 2', async () => { + let base = await mockAggFactory.deploy(8, 3e8) // 3 + let quote = await mockAggFactory.deploy(8, 15e8) // 15 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 8, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 0.2e8 /* 0.2 */, + ) + }) + + it('when result decimals are higher', async () => { + let base = await mockAggFactory.deploy(8, 10e8) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 12, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 2e12 /* 2 */, + ) + }) + + it('when result decimals are lower', async () => { + let base = await mockAggFactory.deploy(8, 10e8) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 6, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 2e6 /* 2 */, + ) + }) + + it('base decimals are higher', async () => { + let base = await mockAggFactory.deploy( + 16, + BigNumber.from('100000000000000000'), + ) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 10, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 2e10 /* 2 */, + ) + }) + + it('base decimals are lower', async () => { + let base = await mockAggFactory.deploy(6, 10e6) // Price = 10 + let quote = await mockAggFactory.deploy(8, 5e8) // Price = 5 + + let derived = await derivedFeedFactory.deploy( + base.address, + quote.address, + 10, + ) + + await expect((await derived.latestRoundData()).answer).to.equal( + 2e10 /* 2 */, + ) + }) + }) +}) diff --git a/core/bridges/bridge_type.go b/core/bridges/bridge_type.go index a3916395f3b..ce9c2c76037 100644 --- a/core/bridges/bridge_type.go +++ b/core/bridges/bridge_type.go @@ -17,7 +17,7 @@ import ( // BridgeTypeRequest is the incoming record used to create a BridgeType type BridgeTypeRequest struct { - Name TaskType `json:"name"` + Name BridgeName `json:"name"` URL models.WebURL `json:"url"` Confirmations uint32 `json:"confirmations"` MinimumContractPayment *assets.Link `json:"minimumContractPayment"` @@ -35,14 +35,14 @@ func (bt BridgeTypeRequest) GetName() string { // SetID is used to set the ID of this structure when deserializing from jsonapi documents. func (bt *BridgeTypeRequest) SetID(value string) error { - name, err := NewTaskType(value) + name, err := ParseBridgeName(value) bt.Name = name return err } // BridgeTypeAuthentication is the record returned in response to a request to create a BridgeType type BridgeTypeAuthentication struct { - Name TaskType + Name BridgeName URL models.WebURL Confirmations uint32 IncomingToken string @@ -53,7 +53,7 @@ type BridgeTypeAuthentication struct { // BridgeType is used for external adapters and has fields for // the name of the adapter and its URL. type BridgeType struct { - Name TaskType + Name BridgeName URL models.WebURL Confirmations uint32 IncomingTokenHash string @@ -140,61 +140,61 @@ func MarshalBridgeMetaData(latestAnswer *big.Int, updatedAt *big.Int) (map[strin return mp, nil } -// TaskType defines what Adapter a TaskSpec will use. -type TaskType string +// BridgeName defines what Adapter a TaskSpec will use. +type BridgeName string -// NewTaskType returns a formatted Task type. -func NewTaskType(val string) (TaskType, error) { +// ParseBridgeName returns a formatted Task type. +func ParseBridgeName(val string) (BridgeName, error) { re := regexp.MustCompile("^[a-zA-Z0-9-_]*$") if !re.MatchString(val) { - return TaskType(""), fmt.Errorf("task type validation: name %v contains invalid characters", val) + return "", fmt.Errorf("task type validation: name %v contains invalid characters", val) } - return TaskType(strings.ToLower(val)), nil + return BridgeName(strings.ToLower(val)), nil } -// MustNewTaskType instantiates a new TaskType, and panics if a bad input is provided. -func MustNewTaskType(val string) TaskType { - tt, err := NewTaskType(val) +// MustParseBridgeName instantiates a new BridgeName, and panics if a bad input is provided. +func MustParseBridgeName(val string) BridgeName { + tt, err := ParseBridgeName(val) if err != nil { - panic(fmt.Sprintf("%v is not a valid TaskType", val)) + panic(fmt.Sprintf("%v is not a valid BridgeName", val)) } return tt } -// UnmarshalJSON converts a bytes slice of JSON to a TaskType. -func (t *TaskType) UnmarshalJSON(input []byte) error { +// UnmarshalJSON converts a bytes slice of JSON to a BridgeName. +func (t *BridgeName) UnmarshalJSON(input []byte) error { var aux string if err := json.Unmarshal(input, &aux); err != nil { return err } - tt, err := NewTaskType(aux) + tt, err := ParseBridgeName(aux) *t = tt return err } -// MarshalJSON converts a TaskType to a JSON byte slice. -func (t TaskType) MarshalJSON() ([]byte, error) { +// MarshalJSON converts a BridgeName to a JSON byte slice. +func (t BridgeName) MarshalJSON() ([]byte, error) { return json.Marshal(t.String()) } -// String returns this TaskType as a string. -func (t TaskType) String() string { +// String returns this BridgeName as a string. +func (t BridgeName) String() string { return string(t) } // Value returns this instance serialized for database storage. -func (t TaskType) Value() (driver.Value, error) { +func (t BridgeName) Value() (driver.Value, error) { return string(t), nil } // Scan reads the database value and returns an instance. -func (t *TaskType) Scan(value interface{}) error { +func (t *BridgeName) Scan(value interface{}) error { temp, ok := value.(string) if !ok { - return fmt.Errorf("unable to convert %v of %T to TaskType", value, value) + return fmt.Errorf("unable to convert %v of %T to BridgeName", value, value) } - *t = TaskType(temp) + *t = BridgeName(temp) return nil } diff --git a/core/bridges/mocks/orm.go b/core/bridges/mocks/orm.go index ada1b523a0a..53d0d0ed5ca 100644 --- a/core/bridges/mocks/orm.go +++ b/core/bridges/mocks/orm.go @@ -131,18 +131,41 @@ func (_m *ORM) ExternalInitiators(offset int, limit int) ([]bridges.ExternalInit } // FindBridge provides a mock function with given fields: name -func (_m *ORM) FindBridge(name bridges.TaskType) (bridges.BridgeType, error) { +func (_m *ORM) FindBridge(name bridges.BridgeName) (bridges.BridgeType, error) { ret := _m.Called(name) var r0 bridges.BridgeType - if rf, ok := ret.Get(0).(func(bridges.TaskType) bridges.BridgeType); ok { + if rf, ok := ret.Get(0).(func(bridges.BridgeName) bridges.BridgeType); ok { r0 = rf(name) } else { r0 = ret.Get(0).(bridges.BridgeType) } var r1 error - if rf, ok := ret.Get(1).(func(bridges.TaskType) error); ok { + if rf, ok := ret.Get(1).(func(bridges.BridgeName) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBridges provides a mock function with given fields: name +func (_m *ORM) FindBridges(name []bridges.BridgeName) ([]bridges.BridgeType, error) { + ret := _m.Called(name) + + var r0 []bridges.BridgeType + if rf, ok := ret.Get(0).(func([]bridges.BridgeName) []bridges.BridgeType); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridges.BridgeType) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]bridges.BridgeName) error); ok { r1 = rf(name) } else { r1 = ret.Error(1) diff --git a/core/bridges/orm.go b/core/bridges/orm.go index eecfeaed3d2..cb7034375e5 100644 --- a/core/bridges/orm.go +++ b/core/bridges/orm.go @@ -13,7 +13,8 @@ import ( //go:generate mockery --name ORM --output ./mocks --case=underscore type ORM interface { - FindBridge(name TaskType) (bt BridgeType, err error) + FindBridge(name BridgeName) (bt BridgeType, err error) + FindBridges(name []BridgeName) (bts []BridgeType, err error) DeleteBridgeType(bt *BridgeType) error BridgeTypes(offset int, limit int) ([]BridgeType, int, error) CreateBridgeType(bt *BridgeType) error @@ -38,12 +39,32 @@ func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig) ORM { } // FindBridge looks up a Bridge by its Name. -func (o *orm) FindBridge(name TaskType) (bt BridgeType, err error) { +// Returns sql.ErrNoRows if name not present +func (o *orm) FindBridge(name BridgeName) (bt BridgeType, err error) { sql := "SELECT * FROM bridge_types WHERE name = $1" err = o.q.Get(&bt, sql, name.String()) return } +// FindBridges looks up multiple bridges in a single query. +// Errors unless all bridges successfully found. Requires at least one bridge. +// Expects all bridges to be unique +func (o *orm) FindBridges(names []BridgeName) (bts []BridgeType, err error) { + sql := "SELECT * FROM bridge_types WHERE name IN (?)" + query, args, err := sqlx.In(sql, names) + if err != nil { + return nil, err + } + err = o.q.Select(&bts, o.q.Rebind(query), args...) + if err != nil { + return nil, err + } + if len(bts) != len(names) { + return nil, errors.Errorf("not all bridges exist, asked for %v, exists %v", names, bts) + } + return +} + // DeleteBridgeType removes the bridge type func (o *orm) DeleteBridgeType(bt *BridgeType) error { query := "DELETE FROM bridge_types WHERE name = $1" diff --git a/core/bridges/orm_test.go b/core/bridges/orm_test.go index 3406fac6788..ad75c9b4e56 100644 --- a/core/bridges/orm_test.go +++ b/core/bridges/orm_test.go @@ -24,19 +24,55 @@ func setupORM(t *testing.T) (*sqlx.DB, bridges.ORM) { return db, orm } +func TestORM_FindBridges(t *testing.T) { + t.Parallel() + _, orm := setupORM(t) + + bt := bridges.BridgeType{ + Name: "bridge1", + URL: cltest.WebURL(t, "https://bridge1.com"), + } + assert.NoError(t, orm.CreateBridgeType(&bt)) + bt2 := bridges.BridgeType{ + Name: "bridge2", + URL: cltest.WebURL(t, "https://bridge2.com"), + } + assert.NoError(t, orm.CreateBridgeType(&bt2)) + bts, err := orm.FindBridges([]bridges.BridgeName{"bridge2", "bridge1"}) + require.NoError(t, err) + require.Equal(t, 2, len(bts)) + + bts, err = orm.FindBridges([]bridges.BridgeName{"bridge1"}) + require.NoError(t, err) + require.Equal(t, 1, len(bts)) + require.Equal(t, "bridge1", bts[0].Name.String()) + + // One invalid bridge errors + bts, err = orm.FindBridges([]bridges.BridgeName{"bridge1", "bridgeX"}) + require.Error(t, err, bts) + + // All invalid bridges error + bts, err = orm.FindBridges([]bridges.BridgeName{"bridgeY", "bridgeX"}) + require.Error(t, err, bts) + + // Requires at least one bridge + bts, err = orm.FindBridges([]bridges.BridgeName{}) + require.Error(t, err, bts) +} + func TestORM_FindBridge(t *testing.T) { t.Parallel() _, orm := setupORM(t) bt := bridges.BridgeType{} - bt.Name = bridges.MustNewTaskType("solargridreporting") + bt.Name = bridges.MustParseBridgeName("solargridreporting") bt.URL = cltest.WebURL(t, "https://denergy.eth") assert.NoError(t, orm.CreateBridgeType(&bt)) cases := []struct { description string - name bridges.TaskType + name bridges.BridgeName want bridges.BridgeType errored bool }{ diff --git a/core/chainlink.Dockerfile b/core/chainlink.Dockerfile index 3826e0ec81b..ab94f573241 100644 --- a/core/chainlink.Dockerfile +++ b/core/chainlink.Dockerfile @@ -38,7 +38,6 @@ RUN go mod download # Env vars needed for chainlink build ARG COMMIT_SHA -ARG ENVIRONMENT COPY core core # Copy over operator-ui build assets to the web module so that we embed them correctly diff --git a/core/chains/evm/chain.go b/core/chains/evm/chain.go index a345a23ac19..91fb59ff0fe 100644 --- a/core/chains/evm/chain.go +++ b/core/chains/evm/chain.go @@ -11,13 +11,13 @@ import ( "go.uber.org/multierr" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink/core/chains/evm/balancemonitor" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/core/chains/evm/monitor" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -28,16 +28,16 @@ import ( //go:generate mockery --name Chain --output ./mocks/ --case=underscore type Chain interface { - services.Service + services.ServiceCtx ID() *big.Int Client() evmclient.Client Config() evmconfig.ChainScopedConfig LogBroadcaster() log.Broadcaster HeadBroadcaster() httypes.HeadBroadcaster - TxManager() bulletprooftxmanager.TxManager + TxManager() txmgr.TxManager HeadTracker() httypes.HeadTracker Logger() logger.Logger - BalanceMonitor() balancemonitor.BalanceMonitor + BalanceMonitor() monitor.BalanceMonitor } var _ Chain = &chain{} @@ -47,12 +47,12 @@ type chain struct { id *big.Int cfg evmconfig.ChainScopedConfig client evmclient.Client - txm bulletprooftxmanager.TxManager + txm txmgr.TxManager logger logger.Logger headBroadcaster httypes.HeadBroadcaster headTracker httypes.HeadTracker logBroadcaster log.Broadcaster - balanceMonitor balancemonitor.BalanceMonitor + balanceMonitor monitor.BalanceMonitor keyStore keystore.Eth } @@ -78,7 +78,7 @@ func newChain(dbchain types.Chain, opts ChainSetOpts) (*chain, error) { client = evmclient.NewNullClient(chainID, l) } else if opts.GenEthClient == nil { var err2 error - client, err2 = newEthClientFromChain(l, dbchain) + client, err2 = newEthClientFromChain(cfg, l, dbchain) if err2 != nil { return nil, errors.Wrapf(err2, "failed to instantiate eth client for chain with ID %s", dbchain.ID.String()) } @@ -107,12 +107,12 @@ func newChain(dbchain types.Chain, opts ChainSetOpts) (*chain, error) { headTracker = opts.GenHeadTracker(dbchain, headBroadcaster) } - var txm bulletprooftxmanager.TxManager + var txm txmgr.TxManager if !cfg.EVMRPCEnabled() { - txm = &bulletprooftxmanager.NullTxManager{ErrMsg: fmt.Sprintf("Ethereum is disabled for chain %d", chainID)} + txm = &txmgr.NullTxManager{ErrMsg: fmt.Sprintf("Ethereum is disabled for chain %d", chainID)} } else if opts.GenTxManager == nil { - checker := &bulletprooftxmanager.CheckerFactory{Client: client} - txm = bulletprooftxmanager.NewBulletproofTxManager(db, client, cfg, opts.KeyStore, opts.EventBroadcaster, l, checker) + checker := &txmgr.CheckerFactory{Client: client} + txm = txmgr.NewTxm(db, client, cfg, opts.KeyStore, opts.EventBroadcaster, l, checker) } else { txm = opts.GenTxManager(dbchain) } @@ -125,9 +125,9 @@ func newChain(dbchain types.Chain, opts ChainSetOpts) (*chain, error) { return nil, err } - var balanceMonitor balancemonitor.BalanceMonitor + var balanceMonitor monitor.BalanceMonitor if cfg.EVMRPCEnabled() && cfg.BalanceMonitorEnabled() { - balanceMonitor = balancemonitor.NewBalanceMonitor(client, opts.KeyStore, l) + balanceMonitor = monitor.NewBalanceMonitor(client, opts.KeyStore, l) headBroadcaster.Subscribe(balanceMonitor) } @@ -164,10 +164,8 @@ func newChain(dbchain types.Chain, opts ChainSetOpts) (*chain, error) { return &c, nil } -func (c *chain) Start() error { +func (c *chain) Start(ctx context.Context) error { return c.StartOnce("Chain", func() (merr error) { - ctx := context.Background() - c.logger.Debugf("Chain: starting with ID %s", c.ID().String()) // Must ensure that EthClient is dialed first because subsequent // services may make eth calls on startup @@ -175,13 +173,13 @@ func (c *chain) Start() error { return errors.Wrap(err, "failed to dial ethclient") } merr = multierr.Combine( - c.txm.Start(), - c.headBroadcaster.Start(), - c.headTracker.Start(), - c.logBroadcaster.Start(), + c.txm.Start(ctx), + c.headBroadcaster.Start(ctx), + c.headTracker.Start(ctx), + c.logBroadcaster.Start(ctx), ) if c.balanceMonitor != nil { - merr = multierr.Combine(merr, c.balanceMonitor.Start()) + merr = multierr.Combine(merr, c.balanceMonitor.Start(ctx)) } if merr != nil { @@ -273,17 +271,17 @@ func (c *chain) Healthy() (merr error) { return } -func (c *chain) ID() *big.Int { return c.id } -func (c *chain) Client() evmclient.Client { return c.client } -func (c *chain) Config() evmconfig.ChainScopedConfig { return c.cfg } -func (c *chain) LogBroadcaster() log.Broadcaster { return c.logBroadcaster } -func (c *chain) HeadBroadcaster() httypes.HeadBroadcaster { return c.headBroadcaster } -func (c *chain) TxManager() bulletprooftxmanager.TxManager { return c.txm } -func (c *chain) HeadTracker() httypes.HeadTracker { return c.headTracker } -func (c *chain) Logger() logger.Logger { return c.logger } -func (c *chain) BalanceMonitor() balancemonitor.BalanceMonitor { return c.balanceMonitor } - -func newEthClientFromChain(lggr logger.Logger, chain types.Chain) (evmclient.Client, error) { +func (c *chain) ID() *big.Int { return c.id } +func (c *chain) Client() evmclient.Client { return c.client } +func (c *chain) Config() evmconfig.ChainScopedConfig { return c.cfg } +func (c *chain) LogBroadcaster() log.Broadcaster { return c.logBroadcaster } +func (c *chain) HeadBroadcaster() httypes.HeadBroadcaster { return c.headBroadcaster } +func (c *chain) TxManager() txmgr.TxManager { return c.txm } +func (c *chain) HeadTracker() httypes.HeadTracker { return c.headTracker } +func (c *chain) Logger() logger.Logger { return c.logger } +func (c *chain) BalanceMonitor() monitor.BalanceMonitor { return c.balanceMonitor } + +func newEthClientFromChain(cfg evmclient.NodeConfig, lggr logger.Logger, chain types.Chain) (evmclient.Client, error) { nodes := chain.Nodes chainID := big.Int(chain.ID) var primaries []evmclient.Node @@ -296,7 +294,7 @@ func newEthClientFromChain(lggr logger.Logger, chain types.Chain) (evmclient.Cli } sendonlys = append(sendonlys, sendonly) } else { - primary, err := newPrimary(lggr, node) + primary, err := newPrimary(cfg, lggr, node) if err != nil { return nil, err } @@ -306,7 +304,7 @@ func newEthClientFromChain(lggr logger.Logger, chain types.Chain) (evmclient.Cli return evmclient.NewClientWithNodes(lggr, primaries, sendonlys, &chainID) } -func newPrimary(lggr logger.Logger, n types.Node) (evmclient.Node, error) { +func newPrimary(cfg evmclient.NodeConfig, lggr logger.Logger, n types.Node) (evmclient.Node, error) { if n.SendOnly { return nil, errors.New("cannot cast send-only node to primary") } @@ -326,7 +324,7 @@ func newPrimary(lggr logger.Logger, n types.Node) (evmclient.Node, error) { httpuri = u } - return evmclient.NewNode(lggr, *wsuri, httpuri, n.Name), nil + return evmclient.NewNode(cfg, lggr, *wsuri, httpuri, n.Name, n.ID, (*big.Int)(&n.EVMChainID)), nil } func newSendOnly(lggr logger.Logger, n types.Node) (evmclient.SendOnlyNode, error) { @@ -341,5 +339,5 @@ func newSendOnly(lggr logger.Logger, n types.Node) (evmclient.SendOnlyNode, erro return nil, errors.Wrap(err, "invalid http uri") } - return evmclient.NewSendOnlyNode(lggr, *httpuri, n.Name), nil + return evmclient.NewSendOnlyNode(lggr, *httpuri, n.Name, (*big.Int)(&n.EVMChainID)), nil } diff --git a/core/chains/evm/chain_set.go b/core/chains/evm/chain_set.go index 43ff6ec6df4..36057f95fd1 100644 --- a/core/chains/evm/chain_set.go +++ b/core/chains/evm/chain_set.go @@ -1,6 +1,7 @@ package evm import ( + "context" "fmt" "math" "math/big" @@ -11,11 +12,12 @@ import ( "github.com/smartcontractkit/sqlx" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/chains/evm/types" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -33,16 +35,21 @@ type ChainConfigUpdater func(*types.ChainCfg) error //go:generate mockery --name ChainSet --output ./mocks/ --case=underscore type ChainSet interface { - services.Service + services.ServiceCtx Get(id *big.Int) (Chain, error) - Add(id *big.Int, config types.ChainCfg) (types.Chain, error) + Add(ctx context.Context, id *big.Int, config types.ChainCfg) (types.Chain, error) Remove(id *big.Int) error Default() (Chain, error) - Configure(id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) + Configure(ctx context.Context, id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) UpdateConfig(id *big.Int, updaters ...ChainConfigUpdater) error Chains() []Chain ChainCount() int ORM() types.ORM + // GetNode et al retrieves Nodes from the ORM and adds additional state info + GetNode(ctx context.Context, id int32) (node evmtypes.Node, err error) + GetNodes(ctx context.Context, offset, limit int) (nodes []evmtypes.Node, count int, err error) + GetNodesForChain(ctx context.Context, chainID utils.Big, offset, limit int) (nodes []evmtypes.Node, count int, err error) + GetNodesByChainIDs(ctx context.Context, chainIDs []utils.Big) (nodes []types.Node, err error) } type chainSet struct { @@ -55,7 +62,7 @@ type chainSet struct { opts ChainSetOpts } -func (cll *chainSet) Start() error { +func (cll *chainSet) Start(ctx context.Context) error { if !cll.opts.Config.EVMEnabled() { cll.logger.Warn("EVM is disabled, no EVM-based chains will be started") return nil @@ -64,8 +71,8 @@ func (cll *chainSet) Start() error { cll.logger.Warn("EVM RPC connections are disabled. Chainlink will not connect to any EVM RPC node.") } for _, c := range cll.Chains() { - if err := c.Start(); err != nil { - cll.logger.Errorw(fmt.Sprintf("EVM: Chain with ID %s failed to start. You will need to fix this issue and restart the Chainlink node before any services that use this chain will work properly. Got error: %v", c.ID(), err), "evmChainID", c.ID(), "err", err) + if err := c.Start(ctx); err != nil { + cll.logger.Criticalw(fmt.Sprintf("EVM: Chain with ID %s failed to start. You will need to fix this issue and restart the Chainlink node before any services that use this chain will work properly. Got error: %v", c.ID(), err), "evmChainID", c.ID(), "err", err) continue } cll.startedChains = append(cll.startedChains, c) @@ -128,7 +135,7 @@ func (cll *chainSet) Default() (Chain, error) { } // Requires a lock on chainsMu -func (cll *chainSet) initializeChain(dbchain *types.Chain) error { +func (cll *chainSet) initializeChain(ctx context.Context, dbchain *types.Chain) error { // preload nodes nodes, _, err := cll.orm.NodesForChain(dbchain.ID, 0, math.MaxInt) if err != nil { @@ -141,14 +148,14 @@ func (cll *chainSet) initializeChain(dbchain *types.Chain) error { if err != nil { return errors.Wrapf(err, "initializeChain: failed to instantiate chain %s", dbchain.ID.String()) } - if err = chain.Start(); err != nil { + if err = chain.Start(ctx); err != nil { return errors.Wrapf(err, "initializeChain: failed to start chain %s", dbchain.ID.String()) } cll.chains[cid] = chain return nil } -func (cll *chainSet) Add(id *big.Int, config types.ChainCfg) (types.Chain, error) { +func (cll *chainSet) Add(ctx context.Context, id *big.Int, config types.ChainCfg) (types.Chain, error) { cll.chainsMu.Lock() defer cll.chainsMu.Unlock() @@ -162,7 +169,7 @@ func (cll *chainSet) Add(id *big.Int, config types.ChainCfg) (types.Chain, error if err != nil { return types.Chain{}, err } - return dbchain, cll.initializeChain(&dbchain) + return dbchain, cll.initializeChain(ctx, &dbchain) } func (cll *chainSet) Remove(id *big.Int) error { @@ -183,7 +190,7 @@ func (cll *chainSet) Remove(id *big.Int) error { return chain.Close() } -func (cll *chainSet) Configure(id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) { +func (cll *chainSet) Configure(ctx context.Context, id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) { cll.chainsMu.Lock() defer cll.chainsMu.Unlock() @@ -205,7 +212,7 @@ func (cll *chainSet) Configure(id *big.Int, enabled bool, config types.ChainCfg) return types.Chain{}, chain.Close() case !exists && enabled: // Chain was toggled to enabled - return dbchain, cll.initializeChain(&dbchain) + return dbchain, cll.initializeChain(ctx, &dbchain) case exists: // Exists in memory, no toggling: Update in-memory chain if err = chain.Config().Configure(config); err != nil { @@ -267,6 +274,75 @@ func (cll *chainSet) ORM() types.ORM { return cll.orm } +func (cll *chainSet) GetNode(ctx context.Context, id int32) (n evmtypes.Node, err error) { + n, err = cll.orm.Node(id, pg.WithParentCtx(ctx)) + if err != nil { + err = errors.Wrap(err, "GetNode failed to load node from DB") + return + } + cll.addStateToNode(&n) + return +} + +func (cll *chainSet) GetNodes(ctx context.Context, offset, limit int) (nodes []evmtypes.Node, count int, err error) { + nodes, count, err = cll.orm.Nodes(offset, limit, pg.WithParentCtx(ctx)) + if err != nil { + err = errors.Wrap(err, "GetNodes failed to load nodes from DB") + return + } + for i := range nodes { + cll.addStateToNode(&nodes[i]) + } + return +} + +func (cll *chainSet) GetNodesForChain(ctx context.Context, chainID utils.Big, offset, limit int) (nodes []evmtypes.Node, count int, err error) { + nodes, count, err = cll.orm.NodesForChain(chainID, offset, limit, pg.WithParentCtx(ctx)) + if err != nil { + err = errors.Wrap(err, "GetNodesForChain failed to load nodes from DB") + return + } + for i := range nodes { + cll.addStateToNode(&nodes[i]) + } + return +} + +func (cll *chainSet) GetNodesByChainIDs(ctx context.Context, chainIDs []utils.Big) (nodes []evmtypes.Node, err error) { + nodes, err = cll.orm.GetNodesByChainIDs(chainIDs, pg.WithParentCtx(ctx)) + if err != nil { + err = errors.Wrap(err, "GetNodesForChain failed to load nodes from DB") + return + } + for i := range nodes { + cll.addStateToNode(&nodes[i]) + } + return +} + +func (cll *chainSet) addStateToNode(n *evmtypes.Node) { + cll.chainsMu.RLock() + chain, exists := cll.chains[n.EVMChainID.String()] + cll.chainsMu.RUnlock() + if !exists { + // The EVM chain is disabled + n.State = "Disabled" + return + } + states := chain.Client().NodeStates() + if states == nil { + n.State = "Unknown" + return + } + state, exists := states[n.ID] + if exists { + n.State = state + return + } + // The node is in the DB and the chain is enabled but it's not running + n.State = "NotLoaded" +} + type ChainSetOpts struct { Config config.GeneralConfig Logger logger.Logger @@ -279,7 +355,7 @@ type ChainSetOpts struct { GenEthClient func(types.Chain) evmclient.Client GenLogBroadcaster func(types.Chain) log.Broadcaster GenHeadTracker func(types.Chain, httypes.HeadBroadcaster) httypes.HeadTracker - GenTxManager func(types.Chain) bulletprooftxmanager.TxManager + GenTxManager func(types.Chain) txmgr.TxManager } func LoadChainSet(opts ChainSetOpts) (ChainSet, error) { @@ -331,7 +407,7 @@ func checkOpts(opts *ChainSetOpts) error { return errors.New("config must be non-nil") } if opts.ORM == nil { - opts.ORM = NewORM(opts.DB) + opts.ORM = NewORM(opts.DB, opts.Logger, opts.Config) } return nil } diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index ac956cbe992..77a772120e2 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -29,6 +29,9 @@ type Client interface { Dial(ctx context.Context) error Close() ChainID() *big.Int + // NodeStates returns a map of node ID->node state + // It might be nil or empty, e.g. for mock clients etc + NodeStates() map[int32]string GetERC20Balance(address common.Address, contractAddress common.Address) (*big.Int, error) GetLINKBalance(linkAddress common.Address, address common.Address) (*assets.Link, error) @@ -38,10 +41,15 @@ type Client interface { Call(result interface{}, method string, args ...interface{}) error CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error BatchCallContext(ctx context.Context, b []rpc.BatchElem) error + // BatchCallContextAll calls BatchCallContext for every single node including + // sendonlys. + // CAUTION: This should only be used for mass re-transmitting transactions, it + // might have unexpected effects to use it for anything else. + BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error // HeadByNumber is a reimplemented version of HeaderByNumber due to a // difference in how block header hashes are calculated by Parity nodes - // running on Kovan. We have to return our own wrapper type to capture the + // running on Kovan. We have to return our own wrapper type to capture the // correct hash from the RPC response. HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) SubscribeNewHead(ctx context.Context, ch chan<- *evmtypes.Head) (ethereum.Subscription, error) @@ -118,6 +126,14 @@ func (client *client) Close() { client.pool.Close() } +func (client *client) NodeStates() (states map[int32]string) { + states = make(map[int32]string) + for _, n := range client.pool.nodes { + states[n.ID()] = n.State().String() + } + return +} + // CallArgs represents the data used to call the balance method of a contract. // "To" is the address of the ERC contract. "Data" is the message sent // to the contract. @@ -201,6 +217,12 @@ func (client *client) EstimateGas(ctx context.Context, call ethereum.CallMsg) (g return client.pool.EstimateGas(ctx, call) } +// SuggestGasPrice calls the RPC node to get a suggested gas price. +// WARNING: It is not recommended to ever use this result for anything +// important. There are a number of issues with asking the RPC node to provide a +// gas estimate; it is not reliable. Unless you really have a good reason to +// use this, you should probably use core node's internal gas estimator +// instead. func (client *client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { return client.pool.SuggestGasPrice(ctx) } @@ -262,7 +284,7 @@ func (client *client) SubscribeNewHead(ctx context.Context, ch chan<- *evmtypes. return csf, nil } -func (client *client) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) { +func (client *client) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) { return client.pool.EthSubscribe(ctx, channel, args...) } @@ -280,6 +302,16 @@ func (client *client) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e return client.pool.BatchCallContext(ctx, b) } +func (client *client) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + return client.pool.BatchCallContextAll(ctx, b) +} + +// SuggestGasTipCap calls the RPC node to get a suggested gas tip cap. +// WARNING: It is not recommended to ever use this result for anything +// important. There are a number of issues with asking the RPC node to provide a +// gas estimate; it is not reliable. Unless you really have a good reason to +// use this, you should probably use core node's internal gas estimator +// instead. func (client *client) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { return client.pool.SuggestGasTipCap(ctx) } diff --git a/core/chains/evm/client/client_test.go b/core/chains/evm/client/client_test.go index 2f77c2faae0..ce1c58aa60b 100644 --- a/core/chains/evm/client/client_test.go +++ b/core/chains/evm/client/client_test.go @@ -29,6 +29,17 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) +func mustNewClient(t *testing.T, wsURL string, sendonlys ...url.URL) evmclient.Client { + return mustNewClientWithChainID(t, wsURL, testutils.FixtureChainID, sendonlys...) +} + +func mustNewClientWithChainID(t *testing.T, wsURL string, chainID *big.Int, sendonlys ...url.URL) evmclient.Client { + cfg := evmclient.TestNodeConfig{} + c, err := evmclient.NewClientWithTestNode(cfg, logger.TestLogger(t), wsURL, nil, sendonlys, 42, chainID) + require.NoError(t, err) + return c +} + func TestEthClient_TransactionReceipt(t *testing.T) { txHash := "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238" @@ -45,16 +56,15 @@ func TestEthClient_TransactionReceipt(t *testing.T) { t.Run("happy path", func(t *testing.T) { result := mustReadResult(t, "../../../testdata/jsonrpc/getTransactionReceipt.json") - wsUrl := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_getTransactionReceipt", method) require.True(t, params.IsArray()) require.Equal(t, txHash, params.Array()[0].String()) return string(result), "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), wsUrl, nil, []url.URL{}, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) hash := common.HexToHash(txHash) @@ -66,16 +76,15 @@ func TestEthClient_TransactionReceipt(t *testing.T) { t.Run("no tx hash, returns ethereum.NotFound", func(t *testing.T) { result := mustReadResult(t, "../../../testdata/jsonrpc/getTransactionReceipt_notFound.json") - wsUrl := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_getTransactionReceipt", method) require.True(t, params.IsArray()) require.Equal(t, txHash, params.Array()[0].String()) return string(result), "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), wsUrl, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) hash := common.HexToHash(txHash) @@ -89,7 +98,7 @@ func TestEthClient_PendingNonceAt(t *testing.T) { address := testutils.NewAddress() - url := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_getTransactionCount", method) require.True(t, params.IsArray()) arr := params.Array() @@ -98,9 +107,8 @@ func TestEthClient_PendingNonceAt(t *testing.T) { return `"0x100"`, "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), url, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) result, err := ethClient.PendingNonceAt(context.Background(), address) @@ -127,16 +135,15 @@ func TestEthClient_BalanceAt(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - url := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_getBalance", method) require.True(t, params.IsArray()) require.Equal(t, strings.ToLower(address.Hex()), strings.ToLower(params.Array()[0].String())) return `"` + hexutil.EncodeBig(test.balance) + `"`, "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), url, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) result, err := ethClient.BalanceAt(context.Background(), address, nil) @@ -167,7 +174,7 @@ func TestEthClient_GetERC20Balance(t *testing.T) { functionSelector := evmtypes.HexToFunctionSelector("0x70a08231") // balanceOf(address) txData := utils.ConcatBytes(functionSelector.Bytes(), common.LeftPadBytes(userAddress.Bytes(), utils.EVMWordByteLen)) - url := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_call", method) require.True(t, params.IsArray()) arr := params.Array() @@ -180,9 +187,8 @@ func TestEthClient_GetERC20Balance(t *testing.T) { return `"` + hexutil.EncodeBig(test.balance) + `"`, "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), url, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) result, err := ethClient.GetERC20Balance(userAddress, contractAddress) @@ -232,7 +238,7 @@ func TestEthClient_HeaderByNumber(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - url := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_getBlockByNumber", method) require.True(t, params.IsArray()) arr := params.Array() @@ -246,9 +252,8 @@ func TestEthClient_HeaderByNumber(t *testing.T) { return test.rpcResp, "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), url, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) defer ethClient.Close() @@ -272,14 +277,13 @@ func TestEthClient_SendTransaction_NoSecondaryURL(t *testing.T) { tx := types.NewTransaction(uint64(42), testutils.NewAddress(), big.NewInt(142), 242, big.NewInt(342), []byte{1, 2, 3}) - url := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_sendRawTransaction", method) return `"` + tx.Hash().Hex() + `"`, "" }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), url, nil, nil, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClient(t, wsURL) + err := ethClient.Dial(context.Background()) require.NoError(t, err) err = ethClient.SendTransaction(context.Background(), tx) @@ -291,7 +295,7 @@ func TestEthClient_SendTransaction_WithSecondaryURLs(t *testing.T) { tx := types.NewTransaction(uint64(42), testutils.NewAddress(), big.NewInt(142), 242, big.NewInt(342), []byte{1, 2, 3}) - wsUrl := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { require.Equal(t, "eth_sendRawTransaction", method) return `"` + tx.Hash().Hex() + `"`, "" }) @@ -303,10 +307,10 @@ func TestEthClient_SendTransaction_WithSecondaryURLs(t *testing.T) { ts := httptest.NewServer(rpcSrv) t.Cleanup(ts.Close) - sendonlyUrl := *cltest.MustParseURL(t, ts.URL) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), wsUrl, nil, []url.URL{sendonlyUrl, sendonlyUrl}, &cltest.FixtureChainID) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + sendonlyURL := *cltest.MustParseURL(t, ts.URL) + ethClient := mustNewClient(t, wsURL, sendonlyURL, sendonlyURL) + defer ethClient.Close() + err := ethClient.Dial(context.Background()) require.NoError(t, err) err = ethClient.SendTransaction(context.Background(), tx) @@ -339,7 +343,7 @@ func TestEthClient_SubscribeNewHead(t *testing.T) { chainId := big.NewInt(123456) const headResult = `{"difficulty":"0xf3a00","extraData":"0xd883010503846765746887676f312e372e318664617277696e","gasLimit":"0xffc001","gasUsed":"0x0","hash":"0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xd1aeb42885a43b72b518182ef893125814811048","mixHash":"0x0f98b15f1a4901a7e9204f3c500a7bd527b3fb2c3340e12176a44b83e414a69e","nonce":"0x0ece08ea8c49dfd9","number":"0x1","parentHash":"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x218","stateRoot":"0xc7b01007a10da045eacb90385887dd0c38fcb5db7393006bdde24b93873c334b","timestamp":"0x58318da2","totalDifficulty":"0x1f3a00","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}` - wsUrl := cltest.NewWSServer(t, chainId, func(method string, params gjson.Result) (string, string) { + wsURL := cltest.NewWSServer(t, chainId, func(method string, params gjson.Result) (string, string) { if method == "eth_unsubscribe" { return "true", "" } @@ -350,9 +354,8 @@ func TestEthClient_SubscribeNewHead(t *testing.T) { return `"0x00"`, headResult }) - ethClient, err := evmclient.NewClient(logger.TestLogger(t), wsUrl, nil, []url.URL{}, chainId) - require.NoError(t, err) - err = ethClient.Dial(context.Background()) + ethClient := mustNewClientWithChainID(t, wsURL, chainId) + err := ethClient.Dial(context.Background()) require.NoError(t, err) headCh := make(chan *evmtypes.Head) diff --git a/core/chains/evm/client/erroring_node.go b/core/chains/evm/client/erroring_node.go index db72b78d0b6..9c3b4c65670 100644 --- a/core/chains/evm/client/erroring_node.go +++ b/core/chains/evm/client/erroring_node.go @@ -4,7 +4,9 @@ import ( "context" "math/big" - ethereum "github.com/ethereum/go-ethereum" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" @@ -17,13 +19,9 @@ type erroringNode struct { errMsg string } -func (e *erroringNode) ChainID(ctx context.Context) (chainID *big.Int, err error) { - return nil, errors.New(e.errMsg) -} +func (e *erroringNode) ChainID() (chainID *big.Int) { return nil } -func (e *erroringNode) Dial(ctx context.Context) error { - return errors.New(e.errMsg) -} +func (e *erroringNode) Start(ctx context.Context) error { return errors.New(e.errMsg) } func (e *erroringNode) Close() {} @@ -99,7 +97,7 @@ func (e *erroringNode) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return nil, errors.New(e.errMsg) } -func (e *erroringNode) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) { +func (e *erroringNode) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) { return nil, errors.New(e.errMsg) } @@ -108,5 +106,11 @@ func (e *erroringNode) String() string { } func (e *erroringNode) State() NodeState { - return NodeStateDead + return NodeStateUnreachable } + +func (e *erroringNode) DeclareOutOfSync() {} +func (e *erroringNode) DeclareInSync() {} +func (e *erroringNode) DeclareUnreachable() {} +func (e *erroringNode) ID() int32 { return 0 } +func (e *erroringNode) NodeStates() map[int32]string { return nil } diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 2855f1dde33..1b283196ec2 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -49,6 +49,7 @@ const ( TooExpensive FeeTooLow FeeTooHigh + TransactionAlreadyMined Fatal ) @@ -130,7 +131,15 @@ var nethermind = ClientErrors{ Fatal: nethermindFatal, } -var clients = []ClientErrors{parity, geth, arbitrum, optimism, substrate, avalanche, nethermind} +// Harmony +// https://github.com/harmony-one/harmony/blob/main/core/tx_pool.go#L49 +var harmonyFatal = regexp.MustCompile("(: |^)(invalid shard|staking message does not match directive message|`from` address of transaction in blacklist|`to` address of transaction in blacklist)$") +var harmony = ClientErrors{ + TransactionAlreadyMined: regexp.MustCompile(`(: |^)transaction already finalized$`), + Fatal: harmonyFatal, +} + +var clients = []ClientErrors{parity, geth, arbitrum, optimism, substrate, avalanche, nethermind, harmony} func (s *SendError) is(errorType int) bool { if s == nil || s.err == nil { @@ -159,6 +168,11 @@ func (s *SendError) IsNonceTooLowError() bool { return s.is(NonceTooLow) } +// IsTransactionAlreadyMined - Harmony returns this error if the transaction has already been mined +func (s *SendError) IsTransactionAlreadyMined() bool { + return s.is(TransactionAlreadyMined) +} + // Geth/parity returns this error if the transaction is already in the node's mempool func (s *SendError) IsTransactionAlreadyInMempool() bool { return s.is(TransactionAlreadyInMempool) diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index 4254e589816..2769209bd14 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -51,6 +51,23 @@ func Test_Eth_Errors(t *testing.T) { } }) + t.Run("IsTransactionAlreadyMined", func(t *testing.T) { + assert.False(t, randomError.IsTransactionAlreadyMined()) + + tests := []errorCase{ + {"transaction already finalized", true, "Harmony"}, + } + + for _, test := range tests { + t.Run(test.network, func(t *testing.T) { + err = evmclient.NewSendErrorS(test.message) + assert.Equal(t, err.IsTransactionAlreadyMined(), test.expect) + err = newSendErrorWrapped(test.message) + assert.Equal(t, err.IsTransactionAlreadyMined(), test.expect) + }) + } + }) + t.Run("IsReplacementUnderpriced", func(t *testing.T) { tests := []errorCase{ @@ -233,6 +250,11 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"call failed: Int256Overflow", true, "Nethermind"}, {"call failed: FailedToResolveSender", true, "Nethermind"}, {"call failed: GasLimitExceeded", true, "Nethermind"}, + + {"invalid shard", true, "Harmony"}, + {"`to` address of transaction in blacklist", true, "Harmony"}, + {"`from` address of transaction in blacklist", true, "Harmony"}, + {"staking message does not match directive message", true, "Harmony"}, } for _, test := range tests { diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go index 86e88471a82..d8a13b51f46 100644 --- a/core/chains/evm/client/helpers_test.go +++ b/core/chains/evm/client/helpers_test.go @@ -10,11 +10,17 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" ) -func init() { - dialRetryInterval = 100 * time.Millisecond +type TestNodeConfig struct { + NoNewHeadsThreshold time.Duration + PollFailureThreshold uint32 + PollInterval time.Duration } -func NewClient(lggr logger.Logger, rpcUrl string, rpcHTTPURL *url.URL, sendonlyRPCURLs []url.URL, chainID *big.Int) (*client, error) { +func (tc TestNodeConfig) NodeNoNewHeadsThreshold() time.Duration { return tc.NoNewHeadsThreshold } +func (tc TestNodeConfig) NodePollFailureThreshold() uint32 { return tc.PollFailureThreshold } +func (tc TestNodeConfig) NodePollInterval() time.Duration { return tc.PollInterval } + +func NewClientWithTestNode(cfg NodeConfig, lggr logger.Logger, rpcUrl string, rpcHTTPURL *url.URL, sendonlyRPCURLs []url.URL, id int32, chainID *big.Int) (*client, error) { parsed, err := url.ParseRequestURI(rpcUrl) if err != nil { return nil, err @@ -24,14 +30,14 @@ func NewClient(lggr logger.Logger, rpcUrl string, rpcHTTPURL *url.URL, sendonlyR return nil, errors.Errorf("ethereum url scheme must be websocket: %s", parsed.String()) } - primaries := []Node{NewNode(lggr, *parsed, rpcHTTPURL, "eth-primary-0")} + primaries := []Node{NewNode(cfg, lggr, *parsed, rpcHTTPURL, "eth-primary-0", id, chainID)} var sendonlys []SendOnlyNode for i, url := range sendonlyRPCURLs { if url.Scheme != "http" && url.Scheme != "https" { return nil, errors.Errorf("sendonly ethereum rpc url scheme must be http(s): %s", url.String()) } - s := NewSendOnlyNode(lggr, url, fmt.Sprintf("eth-sendonly-%d", i)) + s := NewSendOnlyNode(lggr, url, fmt.Sprintf("eth-sendonly-%d", i), chainID) sendonlys = append(sendonlys, s) } diff --git a/core/chains/evm/client/node.go b/core/chains/evm/client/node.go index 9f00cf152e2..90676734cf4 100644 --- a/core/chains/evm/client/node.go +++ b/core/chains/evm/client/node.go @@ -5,25 +5,90 @@ import ( "fmt" "math/big" "net/url" + "strconv" "sync" + "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + uuid "github.com/satori/go.uuid" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/utils" +) + +var ( + promEVMPoolRPCNodeDials = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_dials_total", + Help: "The total number of dials for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeDialsFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_dials_failed", + Help: "The total number of failed dials for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeDialsSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_dials_success", + Help: "The total number of successful dials for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeVerifies = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_verifies", + Help: "The total number of chain ID verifications for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeVerifiesFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_verifies_failed", + Help: "The total number of failed chain ID verifications for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeVerifiesSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_verifies_success", + Help: "The total number of successful chain ID verifications for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + + promEVMPoolRPCNodeCalls = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_calls_total", + Help: "The approximate total number of RPC calls for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeCallsFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_calls_failed", + Help: "The approximate total number of failed RPC calls for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeCallsSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_calls_success", + Help: "The approximate total number of successful RPC calls for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCCallTiming = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "evm_pool_rpc_node_rpc_call_time", + Help: "The duration of an RPC call in nanoseconds", + Buckets: []float64{ + float64(50 * time.Millisecond), + float64(100 * time.Millisecond), + float64(200 * time.Millisecond), + float64(500 * time.Millisecond), + float64(1 * time.Second), + float64(2 * time.Second), + float64(4 * time.Second), + float64(8 * time.Second), + }, + }, []string{"evmChainID", "nodeName", "rpcHost", "isSendOnly", "success", "rpcCallName"}) ) //go:generate mockery --name Node --output ../mocks/ --case=underscore + +// Node represents a client that connects to an ethereum-compatible RPC node type Node interface { - Dial(ctx context.Context) error + Start(ctx context.Context) error Close() - Verify(ctx context.Context, expectedChainID *big.Int) (err error) State() NodeState + // Unique identifier for node + ID() int32 + ChainID() *big.Int CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error BatchCallContext(ctx context.Context, b []rpc.BatchElem) error @@ -42,8 +107,7 @@ type Node interface { CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) HeaderByNumber(context.Context, *big.Int) (*types.Header, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) - EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) - ChainID(ctx context.Context) (chainID *big.Int, err error) + EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) String() string } @@ -54,83 +118,143 @@ type rawclient struct { uri url.URL } -type NodeState int - -const ( - NodeStateUndialed = NodeState(iota) - NodeStateDialed - NodeStateInvalidChainID - NodeStateAlive - NodeStateDead - NodeStateClosed -) - // Node represents one ethereum node. // It must have a ws url and may have a http url type node struct { - ws rawclient - http *rawclient - log logger.Logger - name string - - state NodeState - mu sync.RWMutex -} - -func NewNode(lggr logger.Logger, wsuri url.URL, httpuri *url.URL, name string) Node { + utils.StartStopOnce + ws rawclient + http *rawclient + lfcLog logger.Logger + rpcLog logger.Logger + name string + id int32 + chainID *big.Int + cfg NodeConfig + + state NodeState + stateMu sync.RWMutex + + // chStopInFlight can be closed to immediately cancel all in-flight requests on + // this node. Closing and replacing should be serialized through + // stateMu since it can happen on state transitions as well as node Close. + chStopInFlight chan struct{} + // chStop signals the node to exit + chStop chan struct{} + // wg waits for subsidiary goroutines + wg sync.WaitGroup + + // nLiveNodes is a passed in function that allows this node to + // query a parent object to see how many live nodes there are in total. + // This is done so we can prevent the last alive node in a pool from being + // moved to out-of-sync state. It is better to have one out-of-sync node + // than no nodes at all. + nLiveNodes func() int +} + +// NodeConfig allows configuration of the node +type NodeConfig interface { + NodeNoNewHeadsThreshold() time.Duration + NodePollFailureThreshold() uint32 + NodePollInterval() time.Duration +} + +// NewNode returns a new *node as Node +func NewNode(nodeCfg NodeConfig, lggr logger.Logger, wsuri url.URL, httpuri *url.URL, name string, id int32, chainID *big.Int) Node { n := new(node) n.name = name - n.log = lggr.Named("Node").Named(name).With( - "nodeTier", "primary", - ) + n.id = id + n.chainID = chainID + n.cfg = nodeCfg n.ws.uri = wsuri if httpuri != nil { n.http = &rawclient{uri: *httpuri} } + n.chStopInFlight = make(chan struct{}) + n.chStop = make(chan struct{}) + lggr = lggr.Named("Node").With( + "nodeTier", "primary", + "nodeName", name, + "node", n.String(), + "evmChainID", chainID, + ) + n.lfcLog = lggr.Named("Lifecycle") + n.rpcLog = lggr.Named("RPC") return n } -// Dialling an Alive node is noop -// Can dial Dead or Undialed nodes -// Cannot dial a closed node -func (n *node) Dial(ctx context.Context) error { - ctx, cancel := DefaultQueryCtx(ctx) +// Start dials and verifies the node +// Should only be called once in a node's lifecycle +// Return value is necessary to conform to interface but this will never +// actually return an error. +func (n *node) Start(startCtx context.Context) error { + return n.StartOnce(n.name, func() error { + n.start(startCtx) + return nil + }) +} + +// start initially dials the node and verifies chain ID +// This spins off lifecycle goroutines. +// Not thread-safe. +// Node lifecycle is synchronous: only one goroutine should be running at a +// time. +func (n *node) start(startCtx context.Context) { + if n.state != NodeStateUndialed { + panic(fmt.Sprintf("cannot dial node with state %v", n.state)) + } + + dialCtx, cancel := n.makeQueryCtx(startCtx) defer cancel() + if err := n.dial(dialCtx); err != nil { + n.lfcLog.Errorw("Dial failed: EVM Node is unreachable", "err", err) + n.declareUnreachable() + return + } + n.setState(NodeStateDialed) - n.mu.Lock() - defer n.mu.Unlock() - if n.state == NodeStateAlive || n.state == NodeStateDialed { - return nil - } else if n.state == NodeStateClosed { - return errors.New("cannot dial closed node") + verifyCtx, cancel := n.makeQueryCtx(startCtx) + defer cancel() + if err := n.verify(verifyCtx); errors.Is(err, errInvalidChainID) { + n.lfcLog.Errorw("Verify failed: EVM Node has the wrong chain ID", "err", err) + n.declareInvalidChainID() + return + } else if err != nil { + n.lfcLog.Errorw(fmt.Sprintf("Verify failed: %v", err), "err", err) + n.declareUnreachable() + return } - { - var httpuri string - if n.http != nil { - httpuri = n.http.uri.String() - } - n.log.Debugw("evmclient.Client#Dial(...)", "wsuri", n.ws.uri.String(), "httpuri", httpuri) + n.declareAlive() +} + +// Not thread-safe +// Pure dial: does not mutate node "state" field. +func (n *node) dial(callerCtx context.Context) error { + ctx, cancel := n.makeQueryCtx(callerCtx) + defer cancel() + + promEVMPoolRPCNodeDials.WithLabelValues(n.chainID.String(), n.name).Inc() + lggr := n.lfcLog.With("wsuri", n.ws.uri.Redacted()) + if n.http != nil { + lggr = lggr.With("httpuri", n.http.uri.Redacted()) } + lggr.Debugw("RPC dial: evmclient.Client#dial") - uri := n.ws.uri.String() - wsrpc, err := rpc.DialWebsocket(ctx, uri, "") + wsrpc, err := rpc.DialWebsocket(ctx, n.ws.uri.String(), "") if err != nil { - n.state = NodeStateDead - return errors.Wrapf(err, "error while dialing websocket: %v", uri) + promEVMPoolRPCNodeDialsFailed.WithLabelValues(n.chainID.String(), n.name).Inc() + return errors.Wrapf(err, "error while dialing websocket: %v", n.ws.uri.Redacted()) } var httprpc *rpc.Client if n.http != nil { - uri := n.http.uri.String() - httprpc, err = rpc.DialHTTP(uri) + httprpc, err = rpc.DialHTTP(n.http.uri.String()) if err != nil { - n.state = NodeStateDead - return errors.Wrapf(err, "error while dialing HTTP: %v", uri) + promEVMPoolRPCNodeDialsFailed.WithLabelValues(n.chainID.String(), n.name).Inc() + return errors.Wrapf(err, "error while dialing HTTP: %v", n.http.uri.Redacted()) } } - n.state = NodeStateDialed n.ws.rpc = wsrpc n.ws.geth = ethclient.NewClient(wsrpc) @@ -139,122 +263,203 @@ func (n *node) Dial(ctx context.Context) error { n.http.geth = ethclient.NewClient(httprpc) } + n.lfcLog.Debugw("RPC dial: success") + promEVMPoolRPCNodeDialsSuccess.WithLabelValues(n.chainID.String(), n.name).Inc() + return nil } -func (n *node) Close() { - n.mu.Lock() - defer n.mu.Unlock() - n.state = NodeStateClosed - if n.ws.rpc != nil { - n.ws.rpc.Close() - } -} +var errInvalidChainID = errors.New("invalid chain id") -// Verify checks that all connections to eth nodes match the given chain ID -func (n *node) Verify(ctx context.Context, expectedChainID *big.Int) (err error) { - ctx, cancel := DefaultQueryCtx(ctx) +// verify checks that all connections to eth nodes match the given chain ID +// Not thread-safe +// Pure verify: does not mutate node "state" field. +func (n *node) verify(callerCtx context.Context) (err error) { + ctx, cancel := n.makeQueryCtx(callerCtx) defer cancel() - n.mu.Lock() - defer n.mu.Unlock() - if n.state == NodeStateUndialed { - return errors.New("cannot verify undialed node") + promEVMPoolRPCNodeVerifies.WithLabelValues(n.chainID.String(), n.name).Inc() + promFailed := func() { + promEVMPoolRPCNodeVerifiesFailed.WithLabelValues(n.chainID.String(), n.name).Inc() } - if n.state == NodeStateDead { - return errors.New("cannot verify dead node") + + switch n.state { + case NodeStateDialed, NodeStateOutOfSync, NodeStateInvalidChainID: + default: + panic(fmt.Sprintf("cannot verify node in state %v", n.state)) } var chainID *big.Int if chainID, err = n.ws.geth.ChainID(ctx); err != nil { - n.state = NodeStateInvalidChainID + promFailed() return errors.Wrapf(err, "failed to verify chain ID for node %s", n.name) - } else if chainID.Cmp(expectedChainID) != 0 { - n.state = NodeStateInvalidChainID - return errors.Errorf( + } else if chainID.Cmp(n.chainID) != 0 { + promFailed() + return errors.Wrapf( + errInvalidChainID, "websocket rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", chainID.String(), - expectedChainID.String(), + n.chainID.String(), n.name, ) } if n.http != nil { if chainID, err = n.http.geth.ChainID(ctx); err != nil { - n.state = NodeStateInvalidChainID + promFailed() return errors.Wrapf(err, "failed to verify chain ID for node %s", n.name) - } else if chainID.Cmp(expectedChainID) != 0 { - n.state = NodeStateInvalidChainID - return errors.Errorf( + } else if chainID.Cmp(n.chainID) != 0 { + promFailed() + return errors.Wrapf( + errInvalidChainID, "http rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", chainID.String(), - expectedChainID.String(), + n.chainID.String(), n.name, ) } } - n.state = NodeStateAlive + + promEVMPoolRPCNodeVerifiesSuccess.WithLabelValues(n.chainID.String(), n.name).Inc() + return nil } -func (n *node) State() NodeState { - n.mu.RLock() - defer n.mu.RUnlock() - return n.state +func (n *node) Close() { + err := n.StopOnce(n.name, func() error { + defer n.wg.Wait() + + n.stateMu.Lock() + defer n.stateMu.Unlock() + + close(n.chStop) + n.cancelInflightRequests() + n.state = NodeStateClosed + if n.ws.rpc != nil { + n.ws.rpc.Close() + } + return nil + }) + if err != nil { + panic(err) + } +} + +// cancelInflightRequests closes and replaces the chStopInFlight +// WARNING: NOT THREAD-SAFE +// This must be called from within the n.stateMu lock +func (n *node) cancelInflightRequests() { + close(n.chStopInFlight) + n.chStopInFlight = make(chan struct{}) +} + +// getChStopInflight provides a convenience helper that mutex wraps a +// read to the chStopInFlight +func (n *node) getChStopInflight() chan struct{} { + n.stateMu.RLock() + defer n.stateMu.RUnlock() + return n.chStopInFlight +} + +func (n *node) getRPCDomain() string { + if n.http != nil { + return n.http.uri.Host + } + return n.ws.uri.Host } // RPC wrappers -// TODO: Handle state below -// e.g. need a way to mark a node as "dead" if it fails more than 3 calls in a row -// see: https://app.shortcut.com/chainlinklabs/story/8403/multiple-primary-geth-nodes-with-failover-load-balancer-part-2 +// CallContext implementation func (n *node) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return err + } defer cancel() - - n.log.Debugw("evmclient.Client#Call(...)", + lggr := n.newRqLggr(switching(n)).With( "method", method, "args", args, - "mode", switching(n), ) + + lggr.Debug("RPC call: evmclient.Client#CallContext") + start := time.Now() if n.http != nil { - return n.wrapHTTP(n.http.rpc.CallContext(ctx, result, method, args...)) + err = n.wrapHTTP(n.http.rpc.CallContext(ctx, result, method, args...)) + } else { + err = n.wrapWS(n.ws.rpc.CallContext(ctx, result, method, args...)) } - return n.wrapWS(n.ws.rpc.CallContext(ctx, result, method, args...)) + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "CallContext", + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + + return err } func (n *node) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("nBatchElems", len(b)) - n.log.Debugw("evmclient.Client#BatchCall(...)", - "nBatchElems", len(b), - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#BatchCallContext") + start := time.Now() if n.http != nil { - return n.wrapHTTP(n.http.rpc.BatchCallContext(ctx, b)) + err = n.wrapHTTP(n.http.rpc.BatchCallContext(ctx, b)) + } else { + err = n.wrapWS(n.ws.rpc.BatchCallContext(ctx, b)) } - return n.wrapWS(n.ws.rpc.BatchCallContext(ctx, b)) + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "BatchCallContext", + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + + return err } -func (n *node) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) { - ctx, cancel := DefaultQueryCtx(ctx) +func (n *node) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) { + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr("websocket").With("args", args) + + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") + start := time.Now() + sub, err := n.ws.rpc.EthSubscribe(ctx, channel, args...) + duration := time.Since(start) - n.log.Debugw("evmclient.Client#EthSubscribe", "mode", "websocket") - return n.ws.rpc.EthSubscribe(ctx, channel, args...) + n.logResult(lggr, err, duration, n.getRPCDomain(), "EthSubscribe", + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + + return sub, err } // GethClient wrappers func (n *node) TransactionReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("txHash", txHash) - n.log.Debugw("evmclient.Client#TransactionReceipt(...)", - "txHash", txHash, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#TransactionReceipt") + start := time.Now() if n.http != nil { receipt, err = n.http.geth.TransactionReceipt(ctx, txHash) err = n.wrapHTTP(err) @@ -262,18 +467,28 @@ func (n *node) TransactionReceipt(ctx context.Context, txHash common.Hash) (rece receipt, err = n.ws.geth.TransactionReceipt(ctx, txHash) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "TransactionReceipt", + "receipt", receipt, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) return } func (n *node) HeaderByNumber(ctx context.Context, number *big.Int) (header *types.Header, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("number", number) - n.log.Debugw("evmclient.Client#HeaderByNumber(...)", - "number", n, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#HeaderByNumber") + start := time.Now() if n.http != nil { header, err = n.http.geth.HeaderByNumber(ctx, number) err = n.wrapHTTP(err) @@ -281,31 +496,54 @@ func (n *node) HeaderByNumber(ctx context.Context, number *big.Int) (header *typ header, err = n.ws.geth.HeaderByNumber(ctx, number) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "HeaderByNumber", + "header", header, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) SendTransaction(ctx context.Context, tx *types.Transaction) error { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("tx", tx) - n.log.Debugw("evmclient.Client#SendTransaction(...)", - "tx", tx, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#SendTransaction") + start := time.Now() if n.http != nil { - return n.wrapHTTP(n.http.geth.SendTransaction(ctx, tx)) + err = n.wrapHTTP(n.http.geth.SendTransaction(ctx, tx)) + } else { + err = n.wrapWS(n.ws.geth.SendTransaction(ctx, tx)) } - return n.wrapWS(n.ws.geth.SendTransaction(ctx, tx)) + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "SendTransaction", + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + + return err } func (n *node) PendingNonceAt(ctx context.Context, account common.Address) (nonce uint64, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return 0, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("account", account) - n.log.Debugw("evmclient.Client#PendingNonceAt(...)", - "account", account, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#PendingNonceAt") + start := time.Now() if n.http != nil { nonce, err = n.http.geth.PendingNonceAt(ctx, account) err = n.wrapHTTP(err) @@ -313,18 +551,31 @@ func (n *node) PendingNonceAt(ctx context.Context, account common.Address) (nonc nonce, err = n.ws.geth.PendingNonceAt(ctx, account) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "PendingNonceAt", + "nonce", nonce, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } +// NonceAt is a bit of a misnomer. You might expect it to return the highest +// mined nonce at the given block number, but it actually returns the total +// transaction count which is the highest mined nonce + 1 func (n *node) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (nonce uint64, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return 0, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("account", account, "blockNumber", blockNumber) - n.log.Debugw("evmclient.Client#NonceAt(...)", - "account", account, - "blockNumber", blockNumber, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#NonceAt") + start := time.Now() if n.http != nil { nonce, err = n.http.geth.NonceAt(ctx, account, blockNumber) err = n.wrapHTTP(err) @@ -332,17 +583,28 @@ func (n *node) NonceAt(ctx context.Context, account common.Address, blockNumber nonce, err = n.ws.geth.NonceAt(ctx, account, blockNumber) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "NonceAt", + "nonce", nonce, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) PendingCodeAt(ctx context.Context, account common.Address) (code []byte, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("account", account) - n.log.Debugw("evmclient.Client#PendingCodeAt(...)", - "account", account, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#PendingCodeAt") + start := time.Now() if n.http != nil { code, err = n.http.geth.PendingCodeAt(ctx, account) err = n.wrapHTTP(err) @@ -350,18 +612,28 @@ func (n *node) PendingCodeAt(ctx context.Context, account common.Address) (code code, err = n.ws.geth.PendingCodeAt(ctx, account) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "PendingCodeAt", + "code", code, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) (code []byte, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("account", account, "blockNumber", blockNumber) - n.log.Debugw("evmclient.Client#CodeAt(...)", - "account", account, - "blockNumber", blockNumber, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#CodeAt") + start := time.Now() if n.http != nil { code, err = n.http.geth.CodeAt(ctx, account, blockNumber) err = n.wrapHTTP(err) @@ -369,17 +641,28 @@ func (n *node) CodeAt(ctx context.Context, account common.Address, blockNumber * code, err = n.ws.geth.CodeAt(ctx, account, blockNumber) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "CodeAt", + "code", code, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return 0, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("call", call) - n.log.Debugw("evmclient.Client#EstimateGas(...)", - "call", call, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#EstimateGas") + start := time.Now() if n.http != nil { gas, err = n.http.geth.EstimateGas(ctx, call) err = n.wrapHTTP(err) @@ -387,26 +670,57 @@ func (n *node) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint gas, err = n.ws.geth.EstimateGas(ctx, call) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "EstimateGas", + "gas", gas, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) SuggestGasPrice(ctx context.Context) (price *big.Int, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)) + + lggr.Debug("RPC call: evmclient.Client#SuggestGasPrice") + start := time.Now() + if n.http != nil { + price, err = n.http.geth.SuggestGasPrice(ctx) + err = n.wrapHTTP(err) + } else { + price, err = n.ws.geth.SuggestGasPrice(ctx) + err = n.wrapWS(err) + } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "SuggestGasPrice", + "price", price, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) - n.log.Debugw("evmclient.Client#SuggestGasPrice()", "mode", "websocket") - price, err = n.ws.geth.SuggestGasPrice(ctx) - err = n.wrapWS(err) return } func (n *node) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (val []byte, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("msg", msg, "blockNumber", blockNumber) - n.log.Debugw("evmclient.Client#CallContract()", - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#CallContract") + start := time.Now() if n.http != nil { val, err = n.http.geth.CallContract(ctx, msg, blockNumber) err = n.wrapHTTP(err) @@ -414,18 +728,29 @@ func (n *node) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumb val, err = n.ws.geth.CallContract(ctx, msg, blockNumber) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "CallContract", + "val", val, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (b *types.Block, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("number", number) - n.log.Debugw("evmclient.Client#BlockByNumber(...)", - "number", number, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#BlockByNumber") + start := time.Now() if n.http != nil { b, err = n.http.geth.BlockByNumber(ctx, number) err = n.wrapHTTP(err) @@ -433,18 +758,28 @@ func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (b *types.Blo b, err = n.ws.geth.BlockByNumber(ctx, number) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "BlockByNumber", + "block", b, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (balance *big.Int, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("account", account.Hex(), "blockNumber", blockNumber) - n.log.Debugw("evmclient.Client#BalanceAt(...)", - "account", account, - "blockNumber", blockNumber, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#BalanceAt") + start := time.Now() if n.http != nil { balance, err = n.http.geth.BalanceAt(ctx, account, blockNumber) err = n.wrapHTTP(err) @@ -452,17 +787,28 @@ func (n *node) BalanceAt(ctx context.Context, account common.Address, blockNumbe balance, err = n.ws.geth.BalanceAt(ctx, account, blockNumber) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "BalanceAt", + "balance", balance, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l []types.Log, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)).With("q", q) - n.log.Debugw("evmclient.Client#FilterLogs(...)", - "q", q, - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#FilterLogs") + start := time.Now() if n.http != nil { l, err = n.http.geth.FilterLogs(ctx, q) err = n.wrapHTTP(err) @@ -470,26 +816,51 @@ func (n *node) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l []type l, err = n.ws.geth.FilterLogs(ctx, q) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "FilterLogs", + "log", l, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (sub ethereum.Subscription, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr("websocket").With("q", q) - n.log.Debugw("evmclient.Client#SubscribeFilterLogs(...)", "q", q, "mode", "websocket") + lggr.Debug("RPC call: evmclient.Client#SubscribeFilterLogs") + start := time.Now() sub, err = n.ws.geth.SubscribeFilterLogs(ctx, q, ch) err = n.wrapWS(err) + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "SubscribeFilterLogs", + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } func (n *node) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { - ctx, cancel := DefaultQueryCtx(ctx) + ctx, cancel, err := n.makeLiveQueryCtx(ctx) + if err != nil { + return nil, err + } defer cancel() + lggr := n.newRqLggr(switching(n)) - n.log.Debugw("evmclient.Client#SuggestGasTipCap(...)", - "mode", switching(n), - ) + lggr.Debug("RPC call: evmclient.Client#SuggestGasTipCap") + start := time.Now() if n.http != nil { tipCap, err = n.http.geth.SuggestGasTipCap(ctx) err = n.wrapHTTP(err) @@ -497,40 +868,73 @@ func (n *node) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error tipCap, err = n.ws.geth.SuggestGasTipCap(ctx) err = n.wrapWS(err) } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "SuggestGasTipCap", + "tipCap", tipCap, + "duration", duration, + "rpcDomain", n.getRPCDomain(), + "err", err, + ) + return } -func (n *node) ChainID(ctx context.Context) (chainID *big.Int, err error) { - ctx, cancel := DefaultQueryCtx(ctx) - defer cancel() +func (n *node) ChainID() (chainID *big.Int) { return n.chainID } - n.log.Debugw("evmclient.Client#ChainID(...)") - if n.http != nil { - chainID, err = n.http.geth.ChainID(ctx) - err = n.wrapHTTP(err) +// newRqLggr generates a new logger with a unique request ID +func (n *node) newRqLggr(mode string) logger.Logger { + return n.rpcLog.With( + "requestID", uuid.NewV4(), + "mode", mode, + ) +} + +func (n *node) logResult( + lggr logger.Logger, + err error, + callDuration time.Duration, + rpcDomain, + callName string, + results ...interface{}, +) { + promEVMPoolRPCNodeCalls.WithLabelValues(n.chainID.String(), n.name).Inc() + if err == nil { + promEVMPoolRPCNodeCallsSuccess.WithLabelValues(n.chainID.String(), n.name).Inc() + lggr.Debugw( + fmt.Sprintf("evmclient.Client#%s RPC call success", callName), + results..., + ) } else { - chainID, err = n.ws.geth.ChainID(ctx) - err = n.wrapWS(err) + promEVMPoolRPCNodeCallsFailed.WithLabelValues(n.chainID.String(), n.name).Inc() + lggr.Debugw( + fmt.Sprintf("evmclient.Client#%s RPC call failure", callName), + append(results, "err", err)..., + ) } - return + promEVMPoolRPCCallTiming. + WithLabelValues( + n.chainID.String(), // chain id + n.name, // node name + rpcDomain, // rpc domain + "false", // is send only + strconv.FormatBool(err == nil), // is successful + callName, // rpc call name + ). + Observe(float64(callDuration)) } func (n *node) wrapWS(err error) error { - err = wrap(err, fmt.Sprintf("primary websocket (%s)", n.ws.uri.String())) - if err != nil { - n.log.Debugw("Call failed", "err", err) - } else { - n.log.Trace("Call succeeded") - } + err = wrap(err, fmt.Sprintf("primary websocket (%s)", n.ws.uri.Redacted())) return err } func (n *node) wrapHTTP(err error) error { - err = wrap(err, fmt.Sprintf("primary http (%s)", n.http.uri.String())) + err = wrap(err, fmt.Sprintf("primary http (%s)", n.http.uri.Redacted())) if err != nil { - n.log.Debugw("Call failed", "err", err) + n.rpcLog.Debugw("Call failed", "err", err) } else { - n.log.Trace("Call succeeded") + n.rpcLog.Trace("Call succeeded") } return err } @@ -545,6 +949,42 @@ func wrap(err error, tp string) error { return errors.Wrapf(err, "%s call failed", tp) } +// makeLiveQueryCtx wraps makeQueryCtx but returns error if node is not +// "alive". +func (n *node) makeLiveQueryCtx(parentCtx context.Context) (ctx context.Context, cancel context.CancelFunc, err error) { + // Need to wrap in mutex because state transition can cancel and replace the + // context + n.stateMu.RLock() + if n.state != NodeStateAlive { + err = errors.Errorf("cannot execute RPC call on node with state: %s", n.state) + n.stateMu.RUnlock() + return + } + cancelCh := n.chStopInFlight + n.stateMu.RUnlock() + ctx, cancel = makeQueryCtx(parentCtx, cancelCh) + return +} + +func (n *node) makeQueryCtx(ctx context.Context) (context.Context, context.CancelFunc) { + return makeQueryCtx(ctx, n.getChStopInflight()) +} + +// makeQueryCtx returns a context that cancels if: +// 1. Passed in ctx cancels +// 2. Passed in channel is closed +// 3. Default timeout is reached (queryTimeout) +func makeQueryCtx(ctx context.Context, ch chan struct{}) (context.Context, context.CancelFunc) { + var chCancel, timeoutCancel context.CancelFunc + ctx, chCancel = utils.WithCloseChan(ctx, ch) + ctx, timeoutCancel = context.WithTimeout(ctx, queryTimeout) + cancel := func() { + chCancel() + timeoutCancel() + } + return ctx, cancel +} + func switching(n *node) string { if n.http != nil { return "http" @@ -553,9 +993,13 @@ func switching(n *node) string { } func (n *node) String() string { - s := fmt.Sprintf("(primary)%s:%s", n.name, n.ws.uri.String()) + s := fmt.Sprintf("(primary)%s:%s", n.name, n.ws.uri.Redacted()) if n.http != nil { - s = s + fmt.Sprintf(":%s", n.http.uri.String()) + s = s + fmt.Sprintf(":%s", n.http.uri.Redacted()) } return s } + +func (n *node) ID() int32 { + return n.id +} diff --git a/core/chains/evm/client/node_fsm.go b/core/chains/evm/client/node_fsm.go new file mode 100644 index 00000000000..23fa3979db9 --- /dev/null +++ b/core/chains/evm/client/node_fsm.go @@ -0,0 +1,249 @@ +package client + +import ( + "fmt" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + promEVMPoolRPCNodeTransitionsToAlive = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_transitions_to_alive", + Help: fmt.Sprintf("Total number of times node has transitioned to %s", NodeStateAlive), + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeTransitionsToInSync = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_transitions_to_in_sync", + Help: fmt.Sprintf("Total number of times node has transitioned from %s to %s", NodeStateOutOfSync, NodeStateAlive), + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeTransitionsToOutOfSync = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_transitions_to_out_of_sync", + Help: fmt.Sprintf("Total number of times node has transitioned to %s", NodeStateOutOfSync), + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeTransitionsToUnreachable = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_transitions_to_unreachable", + Help: fmt.Sprintf("Total number of times node has transitioned to %s", NodeStateUnreachable), + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeTransitionsToInvalidChainID = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_transitions_to_invalid_chain_id", + Help: fmt.Sprintf("Total number of times node has transitioned to %s", NodeStateInvalidChainID), + }, []string{"evmChainID", "nodeName"}) +) + +// NodeState represents the current state of the node +// Node is a FSM (finite state machine) +type NodeState int + +func (n NodeState) String() string { + switch n { + case NodeStateUndialed: + return "Undialed" + case NodeStateDialed: + return "Dialed" + case NodeStateInvalidChainID: + return "InvalidChainID" + case NodeStateAlive: + return "Alive" + case NodeStateUnreachable: + return "Unreachable" + case NodeStateOutOfSync: + return "OutOfSync" + case NodeStateClosed: + return "Closed" + default: + return fmt.Sprintf("NodeState(%d)", n) + } +} + +// GoString prints a prettier state +func (n NodeState) GoString() string { + return fmt.Sprintf("NodeState%s(%d)", n.String(), n) +} + +const ( + // NodeStateUndialed is the first state of a virgin node + NodeStateUndialed = NodeState(iota) + // NodeStateDialed is after a node has successfully dialed but before it has verified the correct chain ID + NodeStateDialed + // NodeStateInvalidChainID is after chain ID verification failed + NodeStateInvalidChainID + // NodeStateAlive is a healthy node after chain ID verification succeeded + NodeStateAlive + // NodeStateUnreachable is a node that cannot be dialed or has disconnected + NodeStateUnreachable + // NodeStateOutOfSync is a node that is accepting connections but exceeded + // the failure threshold without sending any new heads. It will be + // disconnected, then put into a revive loop and re-awakened after redial + // if a new head arrives + NodeStateOutOfSync + // NodeStateClosed is after the connection has been closed and the node is at the end of its lifecycle + NodeStateClosed + // nodeStateLen tracks the number of states + nodeStateLen +) + +// allNodeStates represents all possible states a node can be in +var allNodeStates []NodeState + +func init() { + for s := NodeState(0); s < nodeStateLen; s++ { + allNodeStates = append(allNodeStates, s) + } + +} + +// FSM methods + +// State allows reading the current state of the node +func (n *node) State() NodeState { + n.stateMu.RLock() + defer n.stateMu.RUnlock() + return n.state +} + +// setState is only used by internal state management methods. +// This is low-level; care should be taken by the caller to ensure the new state is a valid transition. +// State changes should always be synchronous: only one goroutine at a time should change state. +// n.stateMu should not be locked for long periods of time because external clients expect a timely response from n.State() +func (n *node) setState(s NodeState) { + n.stateMu.Lock() + defer n.stateMu.Unlock() + n.state = s +} + +// declareXXX methods change the state and pass conrol off the new state +// management goroutine + +func (n *node) declareAlive() { + n.transitionToAlive(func() { + n.lfcLog.Infow("RPC Node is online", "nodeState", n.state) + n.wg.Add(1) + go n.aliveLoop() + }) +} + +func (n *node) transitionToAlive(fn func()) { + promEVMPoolRPCNodeTransitionsToAlive.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == NodeStateClosed { + return + } + switch n.state { + case NodeStateDialed, NodeStateInvalidChainID: + n.state = NodeStateAlive + default: + panic(fmt.Sprintf("cannot transition from %#v to %#v", n.state, NodeStateAlive)) + } + fn() +} + +// declareInSync puts a node back into Alive state, allowing it to be used by +// pool consumers again +func (n *node) declareInSync() { + n.transitionToInSync(func() { + n.lfcLog.Infow("RPC Node is back in sync", "nodeState", n.state) + n.wg.Add(1) + go n.aliveLoop() + }) +} + +func (n *node) transitionToInSync(fn func()) { + promEVMPoolRPCNodeTransitionsToAlive.WithLabelValues(n.chainID.String(), n.name).Inc() + promEVMPoolRPCNodeTransitionsToInSync.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == NodeStateClosed { + return + } + switch n.state { + case NodeStateOutOfSync: + n.state = NodeStateAlive + default: + panic(fmt.Sprintf("cannot transition from %#v to %#v", n.state, NodeStateAlive)) + } + fn() +} + +// declareOutOfSync puts a node into OutOfSync state, disconnecting all current +// clients and making it unavailable for use +func (n *node) declareOutOfSync(latestReceivedBlockNumber int64) { + n.transitionToOutOfSync(func() { + n.lfcLog.Errorw("RPC Node is out of sync", "nodeState", n.state) + n.wg.Add(1) + go n.outOfSyncLoop(latestReceivedBlockNumber) + }) +} + +func (n *node) transitionToOutOfSync(fn func()) { + promEVMPoolRPCNodeTransitionsToOutOfSync.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == NodeStateClosed { + return + } + switch n.state { + case NodeStateAlive: + // Need to disconnect all clients subscribed to this node + n.ws.rpc.Close() + n.cancelInflightRequests() + n.state = NodeStateOutOfSync + default: + panic(fmt.Sprintf("cannot transition from %#v to %#v", n.state, NodeStateOutOfSync)) + } + fn() +} + +func (n *node) declareUnreachable() { + n.transitionToUnreachable(func() { + n.lfcLog.Errorw("RPC Node is unreachable", "nodeState", n.state) + n.wg.Add(1) + go n.unreachableLoop() + }) +} + +func (n *node) transitionToUnreachable(fn func()) { + promEVMPoolRPCNodeTransitionsToUnreachable.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == NodeStateClosed { + return + } + switch n.state { + case NodeStateUndialed, NodeStateDialed, NodeStateAlive, NodeStateOutOfSync, NodeStateInvalidChainID: + // Need to disconnect all clients subscribed to this node + n.ws.rpc.Close() + n.cancelInflightRequests() + n.state = NodeStateUnreachable + default: + panic(fmt.Sprintf("cannot transition from %#v to %#v", n.state, NodeStateUnreachable)) + } + fn() +} + +func (n *node) declareInvalidChainID() { + n.transitionToInvalidChainID(func() { + n.lfcLog.Errorw("RPC Node has the wrong chain ID", "nodeState", n.state) + n.wg.Add(1) + go n.invalidChainIDLoop() + }) +} + +func (n *node) transitionToInvalidChainID(fn func()) { + promEVMPoolRPCNodeTransitionsToInvalidChainID.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == NodeStateClosed { + return + } + switch n.state { + case NodeStateDialed: + // Need to disconnect all clients subscribed to this node + n.ws.rpc.Close() + n.cancelInflightRequests() + n.state = NodeStateInvalidChainID + default: + panic(fmt.Sprintf("cannot transition from %#v to %#v", n.state, NodeStateInvalidChainID)) + } + fn() +} diff --git a/core/chains/evm/client/node_fsm_test.go b/core/chains/evm/client/node_fsm_test.go new file mode 100644 index 00000000000..5c7cb7d7bf6 --- /dev/null +++ b/core/chains/evm/client/node_fsm_test.go @@ -0,0 +1,135 @@ +package client + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/logger" +) + +type fnMock struct{ calls int } + +func (fm *fnMock) Fn() { + fm.calls++ +} + +func (fm *fnMock) AssertNotCalled(t *testing.T) { + assert.Equal(t, 0, fm.calls) +} + +func (fm *fnMock) AssertCalled(t *testing.T) { + assert.Greater(t, fm.calls, 0) +} + +func (fm *fnMock) AssertNumberOfCalls(t *testing.T, n int) { + assert.Equal(t, n, fm.calls) +} + +func TestUnit_Node_StateTransitions(t *testing.T) { + s := testutils.NewWSServer(t, testutils.FixtureChainID, func(method string, params gjson.Result) (string, string) { + return "", "" + }) + defer s.Close() + iN := NewNode(TestNodeConfig{}, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, nil) + n := iN.(*node) + + assert.Equal(t, NodeStateUndialed, n.State()) + + t.Run("setState", func(t *testing.T) { + n.setState(NodeStateAlive) + assert.Equal(t, NodeStateAlive, n.state) + n.setState(NodeStateUndialed) + assert.Equal(t, NodeStateUndialed, n.state) + }) + + // must dial to set rpc client for use in state transitions + err := n.dial(context.Background()) + require.NoError(t, err) + + t.Run("transitionToAlive", func(t *testing.T) { + m := new(fnMock) + assert.Panics(t, func() { + n.transitionToAlive(m.Fn) + }) + m.AssertNotCalled(t) + n.setState(NodeStateDialed) + n.transitionToAlive(m.Fn) + m.AssertNumberOfCalls(t, 1) + n.setState(NodeStateInvalidChainID) + n.transitionToAlive(m.Fn) + m.AssertNumberOfCalls(t, 2) + }) + + t.Run("transitionToInSync", func(t *testing.T) { + m := new(fnMock) + n.setState(NodeStateAlive) + assert.Panics(t, func() { + n.transitionToInSync(m.Fn) + }) + m.AssertNotCalled(t) + n.setState(NodeStateOutOfSync) + n.transitionToInSync(m.Fn) + m.AssertCalled(t) + }) + t.Run("transitionToOutOfSync", func(t *testing.T) { + m := new(fnMock) + n.setState(NodeStateOutOfSync) + assert.Panics(t, func() { + n.transitionToOutOfSync(m.Fn) + }) + m.AssertNotCalled(t) + n.setState(NodeStateAlive) + n.transitionToOutOfSync(m.Fn) + m.AssertCalled(t) + }) + t.Run("transitionToUnreachable", func(t *testing.T) { + m := new(fnMock) + n.setState(NodeStateUnreachable) + assert.Panics(t, func() { + n.transitionToUnreachable(m.Fn) + }) + m.AssertNotCalled(t) + n.setState(NodeStateDialed) + n.transitionToUnreachable(m.Fn) + m.AssertNumberOfCalls(t, 1) + n.setState(NodeStateAlive) + n.transitionToUnreachable(m.Fn) + m.AssertNumberOfCalls(t, 2) + n.setState(NodeStateOutOfSync) + n.transitionToUnreachable(m.Fn) + m.AssertNumberOfCalls(t, 3) + n.setState(NodeStateUndialed) + n.transitionToUnreachable(m.Fn) + m.AssertNumberOfCalls(t, 4) + n.setState(NodeStateInvalidChainID) + n.transitionToUnreachable(m.Fn) + m.AssertNumberOfCalls(t, 5) + }) + t.Run("transitionToInvalidChainID", func(t *testing.T) { + m := new(fnMock) + n.setState(NodeStateUnreachable) + assert.Panics(t, func() { + n.transitionToInvalidChainID(m.Fn) + }) + m.AssertNotCalled(t) + n.setState(NodeStateDialed) + n.transitionToInvalidChainID(m.Fn) + m.AssertCalled(t) + }) + t.Run("Close", func(t *testing.T) { + // first attempt panics due to node being unstarted + assert.Panics(t, n.Close) + // must start to allow closing + err := n.StartOnce("test node", func() error { return nil }) + assert.NoError(t, err) + n.Close() + assert.Equal(t, NodeStateClosed, n.State()) + // second attempt panics due to node being stopped twice + assert.Panics(t, n.Close) + }) +} diff --git a/core/chains/evm/client/node_lifecycle.go b/core/chains/evm/client/node_lifecycle.go new file mode 100644 index 00000000000..9fa3f286dc8 --- /dev/null +++ b/core/chains/evm/client/node_lifecycle.go @@ -0,0 +1,377 @@ +package client + +import ( + "context" + "fmt" + "math" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/utils" +) + +var ( + promEVMPoolRPCNodeHighestSeenBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "evm_pool_rpc_node_highest_seen_block", + Help: "The highest seen block for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodeNumSeenBlocks = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_num_seen_blocks", + Help: "The total number of new blocks seen by the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodePolls = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_polls_total", + Help: "The total number of poll checks for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodePollsFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_polls_failed", + Help: "The total number of failed poll checks for the given RPC node", + }, []string{"evmChainID", "nodeName"}) + promEVMPoolRPCNodePollsSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "evm_pool_rpc_node_polls_success", + Help: "The total number of successful poll checks for the given RPC node", + }, []string{"evmChainID", "nodeName"}) +) + +// zombieNodeCheckInterval controls how often to re-check to see if we need to +// state change in case we have to force a state transition due to no available +// nodes. +// NOTE: This only applies to out-of-sync nodes if they are the last available node +func zombieNodeCheckInterval(cfg NodeConfig) time.Duration { + interval := cfg.NodeNoNewHeadsThreshold() + if interval <= 0 || interval > queryTimeout { + interval = queryTimeout + } + return utils.WithJitter(interval) +} + +// Node is a FSM +// Each state has a loop that goes with it, which monitors the node and moves it into another state as necessary. +// Only one loop must run at a time. +// Each loop passes control onto the next loop as it exits, except when the node is Closed which terminates the loop permanently. + +// This handles node lifecycle for the ALIVE state +// Should only be run ONCE per node, after a successful Dial +func (n *node) aliveLoop() { + defer n.wg.Done() + + { + // sanity check + state := n.State() + switch state { + case NodeStateAlive: + case NodeStateClosed: + return + default: + panic(fmt.Sprintf("aliveLoop can only run for node in Alive state, got: %s", state)) + } + } + + noNewHeadsTimeoutThreshold := n.cfg.NodeNoNewHeadsThreshold() + pollFailureThreshold := n.cfg.NodePollFailureThreshold() + pollInterval := n.cfg.NodePollInterval() + + lggr := n.lfcLog.Named("Alive").With("noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold, "pollInterval", pollInterval, "pollFailureThreshold", pollFailureThreshold) + lggr.Tracew("Alive loop starting", "nodeState", n.State()) + + var headsC <-chan *evmtypes.Head + var outOfSyncT *time.Ticker + var outOfSyncTC <-chan time.Time + var sub ethereum.Subscription + var subErrC <-chan error + if noNewHeadsTimeoutThreshold > 0 { + lggr.Debugw("Head liveness checking enabled", "nodeState", n.State()) + var err error + writableCh := make(chan *evmtypes.Head) + sub, err = n.EthSubscribe(context.Background(), writableCh, "newHeads") + headsC = writableCh + if err != nil { + lggr.Errorw("Initial subscribe for liveness checking failed", "nodeState", n.State()) + n.declareUnreachable() + return + } + defer sub.Unsubscribe() + outOfSyncT = time.NewTicker(noNewHeadsTimeoutThreshold) + defer outOfSyncT.Stop() + outOfSyncTC = outOfSyncT.C + subErrC = sub.Err() + } else { + lggr.Debug("Head liveness checking disabled") + } + + var pollCh <-chan time.Time + if pollInterval > 0 { + lggr.Debug("Polling enabled") + pollT := time.NewTicker(pollInterval) + defer pollT.Stop() + pollCh = pollT.C + if pollFailureThreshold > 0 { + // polling can be enabled with no threshold to enable polling but + // the node will not be marked offline regardless of the number of + // poll failures + lggr.Debug("Polling liveness checking enabled") + } + } else { + lggr.Debug("Polling disabled") + } + + var latestReceivedBlockNumber int64 = -1 + var pollFailures uint32 + + for { + select { + case <-n.chStop: + return + case <-pollCh: + var version string + promEVMPoolRPCNodePolls.WithLabelValues(n.chainID.String(), n.name).Inc() + lggr.Tracew("Polling for version", "nodeState", n.State(), "pollFailures", pollFailures) + ctx, cancel := context.WithTimeout(context.Background(), pollInterval) + ctx, cancel2 := n.makeQueryCtx(ctx) + err := n.CallContext(ctx, &version, "web3_clientVersion") + cancel2() + cancel() + if err != nil { + // prevent overflow + if pollFailures < math.MaxUint32 { + promEVMPoolRPCNodePollsFailed.WithLabelValues(n.chainID.String(), n.name).Inc() + pollFailures++ + } + lggr.Warnw(fmt.Sprintf("Poll failure, RPC endpoint %s failed to respond properly", n.String()), "err", err, "pollFailures", pollFailures, "nodeState", n.State()) + } else { + lggr.Tracew("Version poll successful", "nodeState", n.State(), "clientVersion", version) + promEVMPoolRPCNodePollsSuccess.WithLabelValues(n.chainID.String(), n.name).Inc() + pollFailures = 0 + } + if pollFailureThreshold > 0 && pollFailures >= pollFailureThreshold { + lggr.Errorw(fmt.Sprintf("RPC endpoint failed to respond to %d consecutive polls", pollFailures), "pollFailures", pollFailures, "nodeState", n.State()) + if n.nLiveNodes != nil && n.nLiveNodes() < 2 { + lggr.Critical("RPC endpoint failed to respond to polls; but cannot disable this connection because there are no other RPC endpoints, or all other RPC endpoints are dead. Chainlink is now operating in a degraded state and urgent action is required to resolve the issue") + continue + } + n.declareUnreachable() + return + } + case bh, open := <-headsC: + if !open { + lggr.Errorw("Subscription channel unexpectedly closed", "nodeState", n.State()) + n.declareUnreachable() + return + } + promEVMPoolRPCNodeNumSeenBlocks.WithLabelValues(n.chainID.String(), n.name).Inc() + lggr.Tracew("Got head", "head", bh) + if bh.Number > latestReceivedBlockNumber { + promEVMPoolRPCNodeHighestSeenBlock.WithLabelValues(n.chainID.String(), n.name).Set(float64(bh.Number)) + lggr.Tracew("Got higher block number, resetting timer", "latestReceivedBlockNumber", latestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) + latestReceivedBlockNumber = bh.Number + } else { + lggr.Tracew("Ignoring previously seen block number", "latestReceivedBlockNumber", latestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) + } + outOfSyncT.Reset(noNewHeadsTimeoutThreshold) + case err := <-subErrC: + lggr.Errorw("Subscription was terminated", "err", err, "nodeState", n.State()) + n.declareUnreachable() + return + case <-outOfSyncTC: + // We haven't received a head on the channel for at least the + // threshold amount of time, mark it broken + lggr.Errorw(fmt.Sprintf("RPC endpoint detected out of sync; no new heads received for %s (last head received was %v)", noNewHeadsTimeoutThreshold, latestReceivedBlockNumber), "nodeState", n.State(), "latestReceivedBlockNumber", latestReceivedBlockNumber, "noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold) + if n.nLiveNodes != nil && n.nLiveNodes() < 2 { + lggr.Critical("RPC endpoint detected out of sync; but cannot disable this connection because there are no other RPC endpoints, or all other RPC endpoints dead. Chainlink is now operating in a degraded state and urgent action is required to resolve the issue") + // We don't necessarily want to wait the full timeout to check again, we should + // check regularly and log noisily in this state + outOfSyncT.Reset(zombieNodeCheckInterval(n.cfg)) + continue + } + n.declareOutOfSync(latestReceivedBlockNumber) + return + } + } +} + +// outOfSyncLoop takes an OutOfSync node and puts it back to live status if it +// receives a later head than one we have already seen +func (n *node) outOfSyncLoop(stuckAtBlockNumber int64) { + defer n.wg.Done() + + { + // sanity check + state := n.State() + switch state { + case NodeStateOutOfSync: + case NodeStateClosed: + return + default: + panic(fmt.Sprintf("outOfSyncLoop can only run for node in OutOfSync state, got: %s", state)) + } + } + + outOfSyncAt := time.Now() + + lggr := n.lfcLog.Named("OutOfSync") + lggr.Debugw("Trying to revive out-of-sync RPC node", "nodeState", n.State()) + + // Need to redial since out-of-sync nodes are automatically disconnected + err := n.dial(context.Background()) + if err != nil { + lggr.Errorw("Failed to dial out-of-sync RPC node", "nodeState", n.State()) + n.declareUnreachable() + return + } + + // Manually re-verify since out-of-sync nodes are automatically disconnected + err = n.verify(context.Background()) + if err != nil { + lggr.Errorw(fmt.Sprintf("Failed to verify out-of-sync RPC node: %v", err), "err", err) + n.declareInvalidChainID() + return + } + + lggr.Tracew("Successfully subscribed to heads feed on out-of-sync RPC node", "stuckAtBlockNumber", stuckAtBlockNumber, "nodeState", n.State()) + + ch := make(chan *evmtypes.Head) + subCtx, cancel := n.makeQueryCtx(context.Background()) + // raw call here to bypass node state checking + sub, err := n.ws.rpc.EthSubscribe(subCtx, ch, "newHeads") + cancel() + if err != nil { + lggr.Errorw("Failed to subscribe heads on out-of-sync RPC node", "nodeState", n.State(), "err", err) + n.declareUnreachable() + return + } + defer sub.Unsubscribe() + + for { + select { + case <-n.chStop: + return + case head, open := <-ch: + if !open { + lggr.Error("Subscription channel unexpectedly closed", "nodeState", n.State()) + n.declareUnreachable() + return + } + if head.Number > stuckAtBlockNumber { + // unstuck! flip back into alive loop + lggr.Infow(fmt.Sprintf("Received new block for RPC node %s. Node was offline for %s", n.String(), time.Since(outOfSyncAt)), "latestReceivedBlockNumber", head.Number, "nodeState", n.State()) + n.declareInSync() + return + } + lggr.Debugw("Received previously seen block for RPC node, waiting for new block before marking as live again", "stuckAtBlockNumber", stuckAtBlockNumber, "blockNumber", head.Number, "nodeState", n.State()) + case <-time.After(zombieNodeCheckInterval(n.cfg)): + if n.nLiveNodes != nil && n.nLiveNodes() < 1 { + lggr.Critical("RPC endpoint is still out of sync, but there are no other available nodes. This RPC node will be forcibly moved back into the live pool in a degraded state") + n.declareInSync() + return + + } + case err := <-sub.Err(): + lggr.Errorw("Subscription was terminated", "nodeState", n.State(), "err", err) + n.declareUnreachable() + return + } + } +} + +func (n *node) unreachableLoop() { + defer n.wg.Done() + + { + // sanity check + state := n.State() + switch state { + case NodeStateUnreachable: + case NodeStateClosed: + return + default: + panic(fmt.Sprintf("unreachableLoop can only run for node in Unreachable state, got: %s", state)) + } + } + + unreachableAt := time.Now() + + lggr := n.lfcLog.Named("Unreachable") + lggr.Debugw("Trying to revive unreachable RPC node", "nodeState", n.State()) + + dialRetryBackoff := utils.NewRedialBackoff() + + for { + select { + case <-n.chStop: + return + case <-time.After(dialRetryBackoff.Duration()): + lggr.Tracew("Trying to re-dial RPC node", "nodeState", n.State()) + + err := n.dial(context.Background()) + if err != nil { + lggr.Errorw(fmt.Sprintf("Failed to redial RPC node; still unreachable: %v", err), "err", err, "nodeState", n.State()) + continue + } + + n.setState(NodeStateDialed) + + err = n.verify(context.Background()) + if errors.Is(err, errInvalidChainID) { + lggr.Errorw("Failed to redial RPC node; remote endpoint returned the wrong chain ID", "err", err) + n.declareInvalidChainID() + return + } else if err != nil { + lggr.Errorw(fmt.Sprintf("Failed to redial RPC node; verify failed: %v", err), "err", err) + n.declareUnreachable() + return + } + + lggr.Infow(fmt.Sprintf("Successfully redialled and verified RPC node %s. Node was offline for %s", n.String(), time.Since(unreachableAt)), "nodeState", n.State()) + n.declareAlive() + return + } + } +} + +func (n *node) invalidChainIDLoop() { + defer n.wg.Done() + + { + // sanity check + state := n.State() + switch state { + case NodeStateInvalidChainID: + case NodeStateClosed: + return + default: + panic(fmt.Sprintf("invalidChainIDLoop can only run for node in InvalidChainID state, got: %s", state)) + } + } + + invalidAt := time.Now() + + lggr := n.lfcLog.Named("InvalidChainID") + lggr.Debugw(fmt.Sprintf("Periodically re-checking RPC node %s with invalid chain ID", n.String()), "nodeState", n.State()) + + chainIDRecheckBackoff := utils.NewRedialBackoff() + + for { + select { + case <-n.chStop: + return + case <-time.After(chainIDRecheckBackoff.Duration()): + err := n.verify(context.Background()) + if errors.Is(err, errInvalidChainID) { + lggr.Errorw("Failed to verify RPC node; remote endpoint returned the wrong chain ID", "err", err) + continue + } else if err != nil { + lggr.Errorw(fmt.Sprintf("Unexpected error while verifying RPC node chain ID; %v", err), "err", err) + n.declareUnreachable() + return + } + lggr.Infow(fmt.Sprintf("Successfully verified RPC node. Node was offline for %s", time.Since(invalidAt)), "nodeState", n.State()) + n.declareAlive() + return + } + } +} diff --git a/core/chains/evm/client/node_lifecycle_test.go b/core/chains/evm/client/node_lifecycle_test.go new file mode 100644 index 00000000000..144c2c32aac --- /dev/null +++ b/core/chains/evm/client/node_lifecycle_test.go @@ -0,0 +1,553 @@ +package client + +import ( + "fmt" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + "go.uber.org/atomic" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/logger" +) + +func standardHandler(method string, params gjson.Result) (string, string) { + return "", "" +} + +func newTestNode(t *testing.T, cfg NodeConfig) *node { + return newTestNodeWithCallback(t, cfg, standardHandler) +} + +func newTestNodeWithCallback(t *testing.T, cfg NodeConfig, callback testutils.JSONRPCHandler) *node { + s := testutils.NewWSServer(t, testutils.FixtureChainID, callback) + iN := NewNode(cfg, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID) + n := iN.(*node) + t.Cleanup(s.Close) + return n +} + +// dial setups up the node and puts it into the live state, bypassing the +// normal Start() method which would fire off unwanted goroutines +func dial(t *testing.T, n *node) { + ctx := testutils.TestCtx(t) + require.NoError(t, n.dial(ctx)) + n.setState(NodeStateAlive) + start(t, n) +} + +func start(t *testing.T, n *node) { + // must start to allow closing + err := n.StartOnce("test node", func() error { return nil }) + assert.NoError(t, err) +} + +func makeHeadResult(n int) string { + return fmt.Sprintf( + `{"difficulty":"0xf3a00","extraData":"0xd883010503846765746887676f312e372e318664617277696e","gasLimit":"0xffc001","gasUsed":"0x0","hash":"0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xd1aeb42885a43b72b518182ef893125814811048","mixHash":"0x0f98b15f1a4901a7e9204f3c500a7bd527b3fb2c3340e12176a44b83e414a69e","nonce":"0x0ece08ea8c49dfd9","number":"%s","parentHash":"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x218","stateRoot":"0xc7b01007a10da045eacb90385887dd0c38fcb5db7393006bdde24b93873c334b","timestamp":"0x58318da2","totalDifficulty":"0x1f3a00","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}`, + testutils.IntToHex(n), + ) +} + +func makeNewHeadWSMessage(n int) string { + return fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":%s}}`, makeHeadResult(n)) +} + +func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { + t.Run("with no poll and sync timeouts, exits on close", func(t *testing.T) { + pollAndSyncTimeoutsDisabledCfg := TestNodeConfig{} + n := newTestNode(t, pollAndSyncTimeoutsDisabledCfg) + dial(t, n) + + ch := make(chan struct{}) + go func() { + n.IfStarted(func() { + n.wg.Add(1) + n.aliveLoop() + }) + close(ch) + }() + n.Close() + testutils.WaitWithTimeout(t, ch, "expected aliveLoop to exit") + }) + + t.Run("with no poll failures past threshold, stays alive", func(t *testing.T) { + threshold := 5 + cfg := TestNodeConfig{PollFailureThreshold: uint32(threshold), PollInterval: testutils.TestInterval} + var calls atomic.Int32 + n := newTestNodeWithCallback(t, cfg, func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "web3_clientVersion": + defer calls.Inc() + // It starts working right before it hits threshold + if int(calls.Load())+1 >= threshold { + return `"test client version"`, "" + } + return "this will error", "" + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + testutils.AssertEventually(t, func() bool { + // Need to wait for one complete cycle before checking state so add + // 1 to threshold + return int(calls.Load()) > threshold+1 + }) + + assert.Equal(t, NodeStateAlive, n.State()) + }) + + t.Run("with threshold poll failures, transitions to unreachable", func(t *testing.T) { + syncTimeoutsDisabledCfg := TestNodeConfig{PollFailureThreshold: 3, PollInterval: testutils.TestInterval} + n := newTestNode(t, syncTimeoutsDisabledCfg) + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateUnreachable + }) + }) + + t.Run("with threshold poll failures, but we are the last node alive, forcibly keeps it alive", func(t *testing.T) { + threshold := 3 + cfg := TestNodeConfig{PollFailureThreshold: uint32(threshold), PollInterval: testutils.TestInterval} + var calls atomic.Int32 + n := newTestNodeWithCallback(t, cfg, func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "web3_clientVersion": + defer calls.Inc() + return "this will error", "" + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + n.nLiveNodes = func() int { return 1 } + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + testutils.AssertEventually(t, func() bool { + // Need to wait for one complete cycle before checking state so add + // 1 to threshold + return int(calls.Load()) > threshold+1 + }) + + assert.Equal(t, NodeStateAlive, n.State()) + }) + + t.Run("if initial subscribe fails, transitions to unreachable", func(t *testing.T) { + pollDisabledCfg := TestNodeConfig{NoNewHeadsThreshold: testutils.TestInterval} + n := newTestNode(t, pollDisabledCfg) + dial(t, n) + defer n.Close() + + n.wg.Add(1) + n.aliveLoop() + + assert.Equal(t, NodeStateUnreachable, n.State()) + }) + + t.Run("if remote RPC connection is closed transitions to unreachable", func(t *testing.T) { + // NoNewHeadsThreshold needs to be positive but must be very large so + // we don't time out waiting for a new head before we have a chance to + // handle the server disconnect + cfg := TestNodeConfig{NoNewHeadsThreshold: testutils.WaitTimeout(t), PollInterval: 1 * time.Second} + chSubbed := make(chan struct{}) + chPolled := make(chan struct{}) + s := testutils.NewWSServer(t, testutils.FixtureChainID, + func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "eth_subscribe": + select { + case chSubbed <- struct{}{}: + default: + } + return `"0x00"`, makeHeadResult(0) + case "web3_clientVersion": + select { + case chPolled <- struct{}{}: + default: + } + return `"test client version 2"`, "" + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + defer s.Close() + + iN := NewNode(cfg, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID) + n := iN.(*node) + + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") + testutils.WaitWithTimeout(t, chPolled, "timed out waiting for initial poll") + + assert.Equal(t, NodeStateAlive, n.State()) + + // Simulate remote websocket disconnect + // This causes sub.Err() to close + s.Close() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateUnreachable + }) + }) + + t.Run("when no new heads received for threshold, transitions to unreachable", func(t *testing.T) { + pollDisabledCfg := TestNodeConfig{NoNewHeadsThreshold: testutils.TestInterval} + n := newTestNode(t, pollDisabledCfg) + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateUnreachable + }) + }) + + t.Run("when no new heads received for threshold but we are the last live node, forcibly stays alive", func(t *testing.T) { + lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + pollDisabledCfg := TestNodeConfig{NoNewHeadsThreshold: testutils.TestInterval} + s := testutils.NewWSServer(t, testutils.FixtureChainID, + func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "eth_subscribe": + return `"0x00"`, makeHeadResult(0) + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + + defer s.Close() + iN := NewNode(pollDisabledCfg, lggr, *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID) + n := iN.(*node) + n.nLiveNodes = func() int { return 1 } + dial(t, n) + defer n.Close() + + n.wg.Add(1) + go n.aliveLoop() + + // to avoid timing-dependent tests, simply wait for the log message instead + // wait for the log twice to be sure we have fully completed the code path and gone around the loop + testutils.WaitForLogMessageCount(t, observedLogs, "RPC endpoint detected out of sync; but cannot disable this connection because there are no other RPC endpoints, or all other RPC endpoints dead. Chainlink is now operating in a degraded state and urgent action is required to resolve the issue", 2) + + assert.Equal(t, NodeStateAlive, n.State()) + }) +} + +func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { + t.Run("exits on close", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + dial(t, n) + n.setState(NodeStateOutOfSync) + + ch := make(chan struct{}) + go func() { + n.IfStarted(func() { + n.wg.Add(1) + n.aliveLoop() + }) + close(ch) + }() + n.Close() + testutils.WaitWithTimeout(t, ch, "expected outOfSyncLoop to exit") + }) + + t.Run("if initial subscribe fails, transitions to unreachable", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + dial(t, n) + n.setState(NodeStateOutOfSync) + defer n.Close() + + n.wg.Add(1) + n.outOfSyncLoop(0) + + assert.Equal(t, NodeStateUnreachable, n.State()) + }) + + t.Run("transitions to unreachable if remote RPC subscription channel closed", func(t *testing.T) { + cfg := TestNodeConfig{} + chSubbed := make(chan struct{}) + s := testutils.NewWSServer(t, testutils.FixtureChainID, + func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "eth_subscribe": + select { + case chSubbed <- struct{}{}: + default: + } + return `"0x00"`, makeHeadResult(0) + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + defer s.Close() + + iN := NewNode(cfg, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID) + n := iN.(*node) + + dial(t, n) + n.setState(NodeStateOutOfSync) + defer n.Close() + + n.wg.Add(1) + go n.outOfSyncLoop(0) + + testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") + + assert.Equal(t, NodeStateOutOfSync, n.State()) + + // Simulate remote websocket disconnect + // This causes sub.Err() to close + s.Close() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateUnreachable + }) + }) + + t.Run("transitions to alive if it receives a newer head", func(t *testing.T) { + // NoNewHeadsThreshold needs to be positive but must be very large so + // we don't time out waiting for a new head before we have a chance to + // handle the server disconnect + lggr, observedLogs := logger.TestLoggerObserved(t, zap.DebugLevel) + cfg := TestNodeConfig{} + chSubbed := make(chan struct{}) + s := testutils.NewWSServer(t, testutils.FixtureChainID, + func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "eth_subscribe": + select { + case chSubbed <- struct{}{}: + default: + } + return `"0x00"`, makeNewHeadWSMessage(42) + case "eth_unsubscribe": + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + defer s.Close() + + iN := NewNode(cfg, lggr, *s.WSURL(), nil, "test node", 0, testutils.FixtureChainID) + n := iN.(*node) + + start(t, n) + n.setState(NodeStateOutOfSync) + defer n.Close() + + n.wg.Add(1) + go n.outOfSyncLoop(42) + + testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") + + assert.Equal(t, NodeStateOutOfSync, n.State()) + + // heads less than latest seen head are ignored; they do not make the node live + for i := 0; i < 43; i++ { + msg := makeNewHeadWSMessage(i) + s.MustWriteBinaryMessageSync(t, msg) + testutils.WaitForLogMessageCount(t, observedLogs, "Received previously seen block for RPC node", i+1) + assert.Equal(t, NodeStateOutOfSync, n.State()) + } + + msg := makeNewHeadWSMessage(43) + s.MustWriteBinaryMessageSync(t, msg) + + testutils.WaitForLogMessage(t, observedLogs, "Received new block for RPC node") + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateAlive + }) + }) + + t.Run("if no live nodes are available, forcibly marks this one alive again", func(t *testing.T) { + cfg := TestNodeConfig{NoNewHeadsThreshold: testutils.TestInterval} + chSubbed := make(chan struct{}) + s := testutils.NewWSServer(t, testutils.FixtureChainID, + func(method string, params gjson.Result) (respResult string, notifyResult string) { + switch method { + case "eth_subscribe": + select { + case chSubbed <- struct{}{}: + default: + } + return `"0x00"`, makeHeadResult(0) + default: + t.Fatalf("unexpected RPC method: %s", method) + } + return "", "" + }) + defer s.Close() + + iN := NewNode(cfg, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID) + n := iN.(*node) + n.nLiveNodes = func() int { return 0 } + + dial(t, n) + n.setState(NodeStateOutOfSync) + defer n.Close() + + n.wg.Add(1) + go n.outOfSyncLoop(0) + + testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateAlive + }) + }) +} +func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) { + t.Run("exits on close", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + start(t, n) + n.setState(NodeStateUnreachable) + + ch := make(chan struct{}) + go func() { + n.wg.Add(1) + n.unreachableLoop() + close(ch) + }() + n.Close() + testutils.WaitWithTimeout(t, ch, "expected unreachableLoop to exit") + }) + + t.Run("on successful redial and verify, transitions to alive", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + start(t, n) + defer n.Close() + n.setState(NodeStateUnreachable) + n.wg.Add(1) + + go n.unreachableLoop() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateAlive + }) + }) + + t.Run("on successful redial but failed verify, transitions to invalid chain ID", func(t *testing.T) { + cfg := TestNodeConfig{} + s := testutils.NewWSServer(t, testutils.FixtureChainID, standardHandler) + t.Cleanup(s.Close) + lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + iN := NewNode(cfg, lggr, *s.WSURL(), nil, "test node", 0, big.NewInt(42)) + n := iN.(*node) + defer n.Close() + start(t, n) + n.setState(NodeStateUnreachable) + n.wg.Add(1) + + go n.unreachableLoop() + + testutils.WaitForLogMessage(t, observedLogs, "Failed to redial RPC node; remote endpoint returned the wrong chain ID") + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateInvalidChainID + }) + }) + + t.Run("on failed redial, keeps trying to redial", func(t *testing.T) { + cfg := TestNodeConfig{} + lggr, observedLogs := logger.TestLoggerObserved(t, zap.DebugLevel) + iN := NewNode(cfg, lggr, *testutils.MustParseURL(t, "ws://test.invalid"), nil, "test node", 0, big.NewInt(42)) + n := iN.(*node) + defer n.Close() + start(t, n) + n.setState(NodeStateUnreachable) + n.wg.Add(1) + + go n.unreachableLoop() + + testutils.WaitForLogMessageCount(t, observedLogs, "Failed to redial RPC node", 3) + + assert.Equal(t, NodeStateUnreachable, n.State()) + }) +} +func TestUnit_NodeLifecycle_invalidChainIDLoop(t *testing.T) { + t.Run("exits on close", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + start(t, n) + n.setState(NodeStateInvalidChainID) + + ch := make(chan struct{}) + go func() { + n.wg.Add(1) + n.invalidChainIDLoop() + close(ch) + }() + n.Close() + testutils.WaitWithTimeout(t, ch, "expected invalidChainIDLoop to exit") + }) + + t.Run("on successful verify, transitions to alive", func(t *testing.T) { + cfg := TestNodeConfig{} + n := newTestNode(t, cfg) + dial(t, n) + defer n.Close() + n.setState(NodeStateInvalidChainID) + n.wg.Add(1) + + go n.invalidChainIDLoop() + + testutils.AssertEventually(t, func() bool { + return n.State() == NodeStateAlive + }) + }) + + t.Run("on failed verify, keeps checking", func(t *testing.T) { + cfg := TestNodeConfig{} + s := testutils.NewWSServer(t, testutils.FixtureChainID, standardHandler) + t.Cleanup(s.Close) + lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + iN := NewNode(cfg, lggr, *s.WSURL(), nil, "test node", 0, big.NewInt(42)) + n := iN.(*node) + defer n.Close() + dial(t, n) + n.setState(NodeStateUnreachable) + n.wg.Add(1) + + go n.unreachableLoop() + + testutils.WaitForLogMessageCount(t, observedLogs, "Failed to redial RPC node; remote endpoint returned the wrong chain ID", 3) + + assert.Equal(t, NodeStateInvalidChainID, n.State()) + }) +} diff --git a/core/chains/evm/client/node_test.go b/core/chains/evm/client/node_test.go index 1d02ae900c6..42558c8cf47 100644 --- a/core/chains/evm/client/node_test.go +++ b/core/chains/evm/client/node_test.go @@ -2,16 +2,11 @@ package client_test import ( "context" - "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/logger" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tidwall/gjson" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/core/internal/cltest" ) func Test_NodeWrapError(t *testing.T) { @@ -31,69 +26,3 @@ func Test_NodeWrapError(t *testing.T) { assert.EqualError(t, err, "foo call failed: remote eth node timed out: context deadline exceeded") }) } - -func Test_NodeStateTransitions(t *testing.T) { - nInvalid := evmclient.NewNode(logger.TestLogger(t), *cltest.MustParseURL(t, "ws://example.invalid"), nil, "test node") - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (string, string) { - return "", "" - }) - - nValid := evmclient.NewNode(logger.TestLogger(t), *cltest.MustParseURL(t, wsURL), nil, "test node") - - assert.Equal(t, evmclient.NodeStateUndialed, nInvalid.State()) - assert.Equal(t, evmclient.NodeStateUndialed, nValid.State()) - - var err error - - t.Run("Verify before Dial", func(t *testing.T) { - err = nInvalid.Verify(context.Background(), &cltest.FixtureChainID) - require.Error(t, err) - assert.Contains(t, err.Error(), "cannot verify undialed node") - err = nValid.Verify(context.Background(), &cltest.FixtureChainID) - require.Error(t, err) - assert.Contains(t, err.Error(), "cannot verify undialed node") - }) - - assert.Equal(t, evmclient.NodeStateUndialed, nInvalid.State()) - assert.Equal(t, evmclient.NodeStateUndialed, nValid.State()) - - t.Run("Dial state changes", func(t *testing.T) { - err = nInvalid.Dial(context.Background()) - require.Error(t, err) - assert.Contains(t, err.Error(), "error while dialing websocket") - - assert.Equal(t, evmclient.NodeStateDead, nInvalid.State()) - - // make sure that verifying dead node doesn't crash - err = nInvalid.Verify(context.Background(), &cltest.FixtureChainID) - require.Error(t, err) - assert.Contains(t, err.Error(), "cannot verify dead node") - - assert.Equal(t, evmclient.NodeStateDead, nInvalid.State()) - - err = nValid.Dial(context.Background()) - require.NoError(t, err) - - assert.Equal(t, evmclient.NodeStateDialed, nValid.State()) - }) - - t.Run("Verify after dial", func(t *testing.T) { - err = nValid.Verify(context.Background(), big.NewInt(99)) - require.Error(t, err) - assert.Contains(t, err.Error(), "websocket rpc ChainID doesn't match local chain ID: RPC ID=0, local ID=99") - - assert.Equal(t, evmclient.NodeStateInvalidChainID, nValid.State()) - - err = nValid.Verify(context.Background(), &cltest.FixtureChainID) - require.NoError(t, err) - - assert.Equal(t, evmclient.NodeStateAlive, nValid.State()) - }) - - t.Run("Close state changes", func(t *testing.T) { - nInvalid.Close() - assert.Equal(t, evmclient.NodeStateClosed, nInvalid.State()) - nValid.Close() - assert.Equal(t, evmclient.NodeStateClosed, nValid.State()) - }) -} diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index e817ada3973..fbc82ba5a25 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -179,6 +179,14 @@ func (nc *NullClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e return nil } +// BatchCallContextAll implements evmclient.Client interface +func (nc *NullClient) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + return nil +} + func (nc *NullClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { return nil, nil } + +// NodeStates implements evmclient.Client +func (nc *NullClient) NodeStates() map[int32]string { return nil } diff --git a/core/chains/evm/client/pool.go b/core/chains/evm/client/pool.go index b979b92c2b8..b96f6042963 100644 --- a/core/chains/evm/client/pool.go +++ b/core/chains/evm/client/pool.go @@ -7,17 +7,28 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/atomic" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/utils" ) +var ( + // PromEVMPoolRPCNodeStates reports current RPC node state + PromEVMPoolRPCNodeStates = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "evm_pool_rpc_node_states", + Help: "The number of RPC nodes currently in the given state for the given chain", + }, []string{"evmChainID", "state"}) +) + // Pool represents an abstraction over one or more primary nodes // It is responsible for liveness checking and balancing queries across live nodes type Pool struct { @@ -49,22 +60,34 @@ func NewPool(logger logger.Logger, nodes []Node, sendonlys []SendOnlyNode, chain return p } -// Dial dials every node in the pool and verifies their chain IDs are consistent. +// Dial starts every node in the pool func (p *Pool) Dial(ctx context.Context) error { return p.StartOnce("Pool", func() (merr error) { if len(p.nodes) == 0 { return errors.Errorf("no available nodes for chain %s", p.chainID.String()) } for _, n := range p.nodes { - if err := n.Dial(ctx); err != nil { - p.logger.Errorw("Error dialing node", "node", n, "err", err) - } else if err := n.Verify(ctx, p.chainID); err != nil { - p.logger.Errorw("Error verifying node", "node", n, "err", err) + if n.ChainID().Cmp(p.chainID) != 0 { + return errors.Errorf("node %s has chain ID %s which does not match pool chain ID of %s", n.String(), n.ChainID().String(), p.chainID.String()) + } + rawNode, ok := n.(*node) + if ok { + // This is a bit hacky but it allows the node to be aware of + // pool state and prevent certain state transitions that might + // otherwise leave no nodes available. It is better to have one + // node in a degraded state than no nodes at all. + rawNode.nLiveNodes = p.nLiveNodes + } + // node will handle its own redialing and automatic recovery + if err := n.Start(ctx); err != nil { + return err } } for _, s := range p.sendonlys { - // TODO: Deal with sendonly nodes state - err := s.Dial(ctx) + if s.ChainID().Cmp(p.chainID) != 0 { + return errors.Errorf("sendonly node %s has chain ID %s which does not match pool chain ID of %s", s.String(), s.ChainID().String(), p.chainID.String()) + } + err := s.Start(ctx) if err != nil { return err } @@ -76,56 +99,96 @@ func (p *Pool) Dial(ctx context.Context) error { }) } -// dialRetryInterval controls how often we try to reconnect a dead node -var dialRetryInterval = 5 * time.Second +// nLiveNodes returns the number of currently alive nodes +func (p *Pool) nLiveNodes() (nLiveNodes int) { + for _, n := range p.nodes { + if n.State() == NodeStateAlive { + nLiveNodes++ + } + } + return +} func (p *Pool) runLoop() { defer p.wg.Done() - ticker := time.NewTicker(dialRetryInterval) + + p.report() + + // Prometheus' default interval is 15s, set this to under 7.5s to avoid + // aliasing (see: https://en.wikipedia.org/wiki/Nyquist_frequency) + reportInterval := 6500 * time.Millisecond + monitor := time.NewTicker(utils.WithJitter(reportInterval)) + defer monitor.Stop() for { select { + case <-monitor.C: + p.report() case <-p.chStop: return - case <-ticker.C: - // re-dial all dead nodes - func() { - ctx, cancel := utils.ContextFromChan(p.chStop) - defer cancel() - ctx, cancel = context.WithTimeout(ctx, dialRetryInterval) - defer cancel() - // TODO: How does this play with automatic WS reconnects? - p.redialDeadNodes(ctx) - }() } } } -func (p *Pool) redialDeadNodes(ctx context.Context) { - for _, n := range p.nodes { - if n.State() == NodeStateDead { - if err := n.Dial(ctx); err != nil { - p.logger.Errorw(fmt.Sprintf("Failed to redial eth node: %v", err), "err", err, "node", n.String()) - } - } - if n.State() == NodeStateInvalidChainID || n.State() == NodeStateDialed { - if err := n.Verify(ctx, p.chainID); err != nil { - p.logger.Errorw(fmt.Sprintf("Failed to verify eth node: %v", err), "err", err, "node", n.String()) - } +func (p *Pool) report() { + type nodeWithState struct { + Node string + State string + } + + var total, dead int + counts := make(map[NodeState]int) + nodeStates := make([]nodeWithState, len(p.nodes)) + for i, n := range p.nodes { + state := n.State() + nodeStates[i] = nodeWithState{n.String(), state.String()} + total++ + if state != NodeStateAlive { + dead++ } + counts[state]++ + } + for _, state := range allNodeStates { + count := counts[state] + PromEVMPoolRPCNodeStates.WithLabelValues(p.chainID.String(), state.String()).Set(float64(count)) + } + + live := total - dead + p.logger.Tracew(fmt.Sprintf("Pool state: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) + if total == dead { + p.logger.Criticalw(fmt.Sprintf("No EVM primary nodes available: 0/%d nodes are alive", total), "nodeStates", nodeStates) + } else if dead > 0 { + p.logger.Errorw(fmt.Sprintf("At least one EVM primary node is dead: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) } } +// Close tears down the pool and closes all nodes func (p *Pool) Close() { - //nolint:errcheck - p.StopOnce("Pool", func() error { + err := p.StopOnce("Pool", func() error { close(p.chStop) p.wg.Wait() + + var closeWg sync.WaitGroup + closeWg.Add(len(p.nodes)) for _, n := range p.nodes { - n.Close() + go func(node Node) { + defer closeWg.Done() + node.Close() + }(n) } + closeWg.Add(len(p.sendonlys)) + for _, s := range p.sendonlys { + go func(sNode SendOnlyNode) { + defer closeWg.Done() + sNode.Close() + }(s) + } + closeWg.Wait() return nil }) + if err != nil { + panic(err) + } } func (p *Pool) ChainID() *big.Int { @@ -136,6 +199,7 @@ func (p *Pool) getRoundRobin() Node { nodes := p.liveNodes() nNodes := len(nodes) if nNodes == 0 { + p.logger.Critical("No live RPC nodes available") return &erroringNode{errMsg: fmt.Sprintf("no live nodes available for chain %s", p.chainID.String())} } @@ -163,6 +227,41 @@ func (p *Pool) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { return p.getRoundRobin().BatchCallContext(ctx, b) } +// BatchCallContextAll calls BatchCallContext for every single node including +// sendonlys. +// CAUTION: This should only be used for mass re-transmitting transactions, it +// might have unexpected effects to use it for anything else. +func (p *Pool) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + var wg sync.WaitGroup + defer wg.Wait() + + main := p.getRoundRobin() + var all []SendOnlyNode + for _, n := range p.nodes { + all = append(all, n) + } + all = append(all, p.sendonlys...) + for _, n := range all { + if n == main { + // main node is used at the end for the return value + continue + } + // Parallel call made to all other nodes with ignored return value + wg.Add(1) + go func(n SendOnlyNode) { + defer wg.Done() + err := n.BatchCallContext(ctx, b) + if err != nil { + p.logger.Debugw("Secondary node BatchCallContext failed", "err", err) + } else { + p.logger.Trace("Secondary node BatchCallContext success") + } + }(n) + } + + return main.BatchCallContext(ctx, b) +} + // Wrapped Geth client methods func (p *Pool) SendTransaction(ctx context.Context, tx *types.Transaction) error { main := p.getRoundRobin() @@ -181,17 +280,29 @@ func (p *Pool) SendTransaction(ctx context.Context, tx *types.Transaction) error // in case they are unreliable/slow. // It is purely a "best effort" send. // Resource is not unbounded because the default context has a timeout. - go func(n SendOnlyNode, txCp types.Transaction) { - err := NewSendError(n.SendTransaction(context.Background(), &txCp)) - p.logger.Debugw("Sendonly node sent transaction", "name", n.String(), "tx", tx, "err", err) - if err == nil || err.IsNonceTooLowError() || err.IsTransactionAlreadyInMempool() { - // Nonce too low or transaction known errors are expected since - // the primary SendTransaction may well have succeeded already - return - } - - p.logger.Warnw("Eth client returned error", "name", n.String(), "err", err, "tx", tx) - }(n, *tx) // copy tx here in case it is mutated after the function returns + ok := p.IfNotStopped(func() { + // Must wrap inside IfNotStopped to avoid waitgroup racing with Close + p.wg.Add(1) + go func(n SendOnlyNode, txCp types.Transaction) { + defer p.wg.Done() + timeoutCtx, cancel := DefaultQueryCtx() + defer cancel() + sendCtx, cancel2 := utils.WithCloseChan(timeoutCtx, p.chStop) + defer cancel2() + err := NewSendError(n.SendTransaction(sendCtx, &txCp)) + p.logger.Debugw("Sendonly node sent transaction", "name", n.String(), "tx", tx, "err", err) + if err == nil || err.IsNonceTooLowError() || err.IsTransactionAlreadyMined() || err.IsTransactionAlreadyInMempool() { + // Nonce too low or transaction known errors are expected since + // the primary SendTransaction may well have succeeded already + return + } + + p.logger.Warnw("Eth client returned error", "name", n.String(), "err", err, "tx", tx) + }(n, *tx) // copy tx here in case it is mutated after the function returns + }) + if !ok { + p.logger.Debug("Cannot send transaction on sendonly node; pool is stopped", "node", n.String()) + } } return main.SendTransaction(ctx, tx) @@ -254,6 +365,7 @@ func (p *Pool) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return p.getRoundRobin().SuggestGasTipCap(ctx) } -func (p *Pool) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) { +// EthSubscribe implements evmclient.Client +func (p *Pool) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) { return p.getRoundRobin().EthSubscribe(ctx, channel, args...) } diff --git a/core/chains/evm/client/pool_test.go b/core/chains/evm/client/pool_test.go index b1cd9897889..3594bcbb1c4 100644 --- a/core/chains/evm/client/pool_test.go +++ b/core/chains/evm/client/pool_test.go @@ -6,58 +6,126 @@ import ( "net/http/httptest" "net/url" "testing" - "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - "github.com/pkg/errors" + promtestutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/test-go/testify/mock" "github.com/tidwall/gjson" + "go.uber.org/zap" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" ) func TestPool_Dial(t *testing.T) { tests := []struct { - name string - presetID *big.Int - nodes []chainIDResps - sendNodes []chainIDResp - wantErr bool - errStr string + name string + poolChainID *big.Int + nodeChainID int64 + sendNodeChainID int64 + nodes []chainIDResps + sendNodes []chainIDResp + errStr string }{ { - name: "no nodes", - presetID: &cltest.FixtureChainID, - nodes: []chainIDResps{}, - sendNodes: []chainIDResp{}, - wantErr: true, - errStr: "no available nodes for chain 0", + name: "no nodes", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: testutils.FixtureChainID.Int64(), + nodes: []chainIDResps{}, + sendNodes: []chainIDResp{}, + errStr: "no available nodes for chain 0", }, { - name: "normal", - presetID: &cltest.FixtureChainID, + name: "normal", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: testutils.FixtureChainID.Int64(), nodes: []chainIDResps{ - {ws: chainIDResp{1, nil}}, + {ws: chainIDResp{testutils.FixtureChainID.Int64(), nil}}, }, sendNodes: []chainIDResp{ - {1, nil}, + {testutils.FixtureChainID.Int64(), nil}, }, }, { - name: "normal preset", - presetID: big.NewInt(1), + name: "node has wrong chain ID compared to pool", + poolChainID: testutils.FixtureChainID, + nodeChainID: 42, + sendNodeChainID: testutils.FixtureChainID.Int64(), nodes: []chainIDResps{ {ws: chainIDResp{1, nil}}, }, sendNodes: []chainIDResp{ {1, nil}, }, + errStr: "has chain ID 42 which does not match pool chain ID of 0", + }, + { + name: "sendonly node has wrong chain ID compared to pool", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: 42, + nodes: []chainIDResps{ + {ws: chainIDResp{testutils.FixtureChainID.Int64(), nil}}, + }, + sendNodes: []chainIDResp{ + {testutils.FixtureChainID.Int64(), nil}, + }, + errStr: "has chain ID 42 which does not match pool chain ID of 0", + }, + { + name: "remote RPC has wrong chain ID for primary node (ws) - no error, it will go into retry loop", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: testutils.FixtureChainID.Int64(), + nodes: []chainIDResps{ + { + ws: chainIDResp{42, nil}, + http: &chainIDResp{testutils.FixtureChainID.Int64(), nil}, + }, + }, + sendNodes: []chainIDResp{ + {testutils.FixtureChainID.Int64(), nil}, + }, + }, + { + name: "remote RPC has wrong chain ID for primary node (http) - no error, it will go into retry loop", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: testutils.FixtureChainID.Int64(), + nodes: []chainIDResps{ + { + ws: chainIDResp{testutils.FixtureChainID.Int64(), nil}, + http: &chainIDResp{42, nil}, + }, + }, + sendNodes: []chainIDResp{ + {testutils.FixtureChainID.Int64(), nil}, + }, + }, + { + name: "remote RPC has wrong chain ID for sendonly node", + poolChainID: testutils.FixtureChainID, + nodeChainID: testutils.FixtureChainID.Int64(), + sendNodeChainID: testutils.FixtureChainID.Int64(), + nodes: []chainIDResps{ + {ws: chainIDResp{testutils.FixtureChainID.Int64(), nil}}, + }, + sendNodes: []chainIDResp{ + {42, nil}, + }, + // TODO: Followup; sendonly nodes should not halt if they fail to + // dail on startup; instead should go into retry loop like + // primaries + // See: https://app.shortcut.com/chainlinklabs/story/31338/sendonly-nodes-should-not-halt-node-boot-if-they-fail-to-dial-instead-should-have-retry-loop-like-primaries + errStr: "sendonly rpc ChainID doesn't match local chain ID: RPC ID=42, local ID=0", }, } for _, test := range tests { @@ -68,15 +136,15 @@ func TestPool_Dial(t *testing.T) { nodes := make([]evmclient.Node, len(test.nodes)) for i, n := range test.nodes { - nodes[i] = n.newNode(t) + nodes[i] = n.newNode(t, test.nodeChainID) } sendNodes := make([]evmclient.SendOnlyNode, len(test.sendNodes)) for i, n := range test.sendNodes { - sendNodes[i] = n.newSendOnlyNode(t) + sendNodes[i] = n.newSendOnlyNode(t, test.sendNodeChainID) } - p := evmclient.NewPool(logger.TestLogger(t), nodes, sendNodes, test.presetID) + p := evmclient.NewPool(logger.TestLogger(t), nodes, sendNodes, test.poolChainID) err := p.Dial(ctx) - if test.wantErr { + if test.errStr != "" { require.Error(t, err) assert.Contains(t, err.Error(), test.errStr) } else { @@ -86,53 +154,14 @@ func TestPool_Dial(t *testing.T) { } } -func TestPool_Dial_Errors(t *testing.T) { - t.Run("starts and kicks off retry loop even if dial errors", func(t *testing.T) { - node := new(evmmocks.Node) - node.On("String").Return("node").Maybe() - node.On("Close").Maybe() - node.Test(t) - nodes := []evmclient.Node{node} - p := newPool(t, nodes) - - node.On("Dial", mock.Anything).Return(errors.New("error")) - - err := p.Dial(context.Background()) - require.NoError(t, err) - - p.Close() - - node.AssertExpectations(t) - }) - - t.Run("starts and kicks off retry loop even on verification errors", func(t *testing.T) { - node := new(evmmocks.Node) - node.On("String").Return("node").Maybe() - node.On("Close").Maybe() - node.Test(t) - nodes := []evmclient.Node{node} - p := newPool(t, nodes) - - node.On("Dial", mock.Anything).Return(nil) - node.On("Verify", mock.Anything, &cltest.FixtureChainID).Return(errors.New("error")) - - err := p.Dial(context.Background()) - require.NoError(t, err) - - p.Close() - - node.AssertExpectations(t) - }) -} - type chainIDResp struct { chainID int64 err error } -func (r *chainIDResp) newSendOnlyNode(t *testing.T) evmclient.SendOnlyNode { +func (r *chainIDResp) newSendOnlyNode(t *testing.T, nodeChainID int64) evmclient.SendOnlyNode { httpURL := r.newHTTPServer(t) - return evmclient.NewSendOnlyNode(logger.TestLogger(t), *httpURL, t.Name()) + return evmclient.NewSendOnlyNode(logger.TestLogger(t), *httpURL, t.Name(), big.NewInt(nodeChainID)) } func (r *chainIDResp) newHTTPServer(t *testing.T) *url.URL { rpcSrv := rpc.NewServer() @@ -149,9 +178,10 @@ func (r *chainIDResp) newHTTPServer(t *testing.T) *url.URL { type chainIDResps struct { ws chainIDResp http *chainIDResp + id int32 } -func (r *chainIDResps) newNode(t *testing.T) evmclient.Node { +func (r *chainIDResps) newNode(t *testing.T, nodeChainID int64) evmclient.Node { ws := cltest.NewWSServer(t, big.NewInt(r.ws.chainID), func(method string, params gjson.Result) (string, string) { t.Errorf("Unexpected method call: %s(%s)", method, params) return "", "" @@ -165,7 +195,8 @@ func (r *chainIDResps) newNode(t *testing.T) evmclient.Node { httpURL = r.http.newHTTPServer(t) } - return evmclient.NewNode(logger.TestLogger(t), *wsURL, httpURL, t.Name()) + defer func() { r.id++ }() + return evmclient.NewNode(evmclient.TestNodeConfig{}, logger.TestLogger(t), *wsURL, httpURL, t.Name(), r.id, big.NewInt(nodeChainID)) } type chainIDService struct { @@ -183,8 +214,8 @@ func newPool(t *testing.T, nodes []evmclient.Node) *evmclient.Pool { return evmclient.NewPool(logger.TestLogger(t), nodes, []evmclient.SendOnlyNode{}, &cltest.FixtureChainID) } -func TestPool_RunLoop(t *testing.T) { - t.Run("with several nodes and different types of errors", func(t *testing.T) { +func TestUnit_Pool_RunLoop(t *testing.T) { + t.Run("reports node states to prometheus", func(t *testing.T) { n1 := new(evmmocks.Node) n1.Test(t) n2 := new(evmmocks.Node) @@ -192,7 +223,9 @@ func TestPool_RunLoop(t *testing.T) { n3 := new(evmmocks.Node) n3.Test(t) nodes := []evmclient.Node{n1, n2, n3} - p := newPool(t, nodes) + + lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + p := evmclient.NewPool(lggr, nodes, []evmclient.SendOnlyNode{}, &cltest.FixtureChainID) n1.On("String").Maybe().Return("n1") n2.On("String").Maybe().Return("n2") @@ -202,37 +235,87 @@ func TestPool_RunLoop(t *testing.T) { n2.On("Close").Maybe() n3.On("Close").Maybe() - wait := make(chan struct{}) - // n1 succeeds - n1.On("Dial", mock.Anything).Return(nil).Once() + // n1 is alive + n1.On("Start", mock.Anything).Return(nil).Once() n1.On("Verify", mock.Anything, &cltest.FixtureChainID).Return(nil).Once() n1.On("State").Return(evmclient.NodeStateAlive) - // n2 fails once then succeeds in runloop - n2.On("Dial", mock.Anything).Return(errors.New("first error")).Once() - n2.On("State").Return(evmclient.NodeStateDead) - // n3 succeeds dial then fails verification - n3.On("Dial", mock.Anything).Return(nil).Once() - n3.On("State").Return(evmclient.NodeStateDialed) - n3.On("Verify", mock.Anything, &cltest.FixtureChainID).Return(errors.New("Verify error")).Once() - n3.On("Verify", mock.Anything, &cltest.FixtureChainID).Once().Return(nil).Run(func(_ mock.Arguments) { - close(wait) - }) - - // Handle spurious extra calls after - n2.On("Dial", mock.Anything).Maybe().Return(nil) - n3.On("Verify", mock.Anything, mock.Anything).Maybe().Return(nil) + n1.On("ChainID").Return(testutils.FixtureChainID).Once() + // n2 is unreachable + n2.On("Start", mock.Anything).Return(nil).Once() + n2.On("Verify", mock.Anything, &cltest.FixtureChainID).Return(nil).Once() + n2.On("State").Return(evmclient.NodeStateUnreachable) + n2.On("ChainID").Return(testutils.FixtureChainID).Once() + // n3 is out of sync + n3.On("Start", mock.Anything).Return(nil).Once() + n3.On("Verify", mock.Anything, &cltest.FixtureChainID).Return(nil).Once() + n3.On("State").Return(evmclient.NodeStateOutOfSync) + n3.On("ChainID").Return(testutils.FixtureChainID).Once() require.NoError(t, p.Dial(context.Background())) + defer p.Close() - select { - case <-wait: - case <-time.After(cltest.WaitTimeout(t)): - t.Fatal("timed out waiting for Dial call") - } - p.Close() + testutils.WaitForLogMessage(t, observedLogs, "At least one EVM primary node is dead") - n1.AssertExpectations(t) - n2.AssertExpectations(t) + testutils.AssertEventually(t, func() bool { + totalReported := promtestutil.CollectAndCount(evmclient.PromEVMPoolRPCNodeStates) + if totalReported < 3 { + return false + } + if promtestutil.ToFloat64(evmclient.PromEVMPoolRPCNodeStates.WithLabelValues("0", "Alive")) < 1.0 { + return false + } + if promtestutil.ToFloat64(evmclient.PromEVMPoolRPCNodeStates.WithLabelValues("0", "Unreachable")) < 1.0 { + return false + } + if promtestutil.ToFloat64(evmclient.PromEVMPoolRPCNodeStates.WithLabelValues("0", "OutOfSync")) < 1.0 { + return false + } + return true + }) }) } + +func TestUnit_Pool_BatchCallContextAll(t *testing.T) { + var mockNodes []*evmmocks.Node + var mockSendonlys []*evmmocks.SendOnlyNode + var nodes []evmclient.Node + var sendonlys []evmclient.SendOnlyNode + + nodeCount := 2 + sendOnlyCount := 3 + + b := []rpc.BatchElem{ + rpc.BatchElem{Method: "method", Args: []interface{}{1, false}}, + rpc.BatchElem{Method: "method2"}, + } + + ctx := testutils.Context(t) + + for i := 0; i < nodeCount; i++ { + node := new(evmmocks.Node) + node.On("State").Return(evmclient.NodeStateAlive) + node.Test(t) + node.On("BatchCallContext", ctx, b).Return(nil).Once() + nodes = append(nodes, node) + mockNodes = append(mockNodes, node) + } + for i := 0; i < sendOnlyCount; i++ { + s := new(evmmocks.SendOnlyNode) + s.Test(t) + s.On("BatchCallContext", ctx, b).Return(nil).Once() + sendonlys = append(sendonlys, s) + mockSendonlys = append(mockSendonlys, s) + } + + p := evmclient.NewPool(logger.TestLogger(t), nodes, sendonlys, &cltest.FixtureChainID) + + p.BatchCallContextAll(ctx, b) + + for _, n := range mockNodes { + n.AssertExpectations(t) + } + for _, s := range mockSendonlys { + s.AssertExpectations(t) + } +} diff --git a/core/chains/evm/client/send_only_node.go b/core/chains/evm/client/send_only_node.go index a45c51de2e9..43a766dfcef 100644 --- a/core/chains/evm/client/send_only_node.go +++ b/core/chains/evm/client/send_only_node.go @@ -5,21 +5,27 @@ import ( "fmt" "math/big" "net/url" + "strconv" + "time" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/utils" ) //go:generate mockery --name SendOnlyNode --output ../mocks/ --case=underscore // SendOnlyNode represents one ethereum node used as a sendonly type SendOnlyNode interface { - Dial(context.Context) error - Verify(ctx context.Context, expectedChainID *big.Int) (err error) - ChainID(ctx context.Context) (chainID *big.Int, err error) + // Start may attempt to connect to the node, but should only return error for misconfiguration - never for temporary errors. + Start(context.Context) error + Close() + + ChainID() (chainID *big.Int) SendTransaction(ctx context.Context, tx *types.Transaction) error BatchCallContext(ctx context.Context, b []rpc.BatchElem) error @@ -30,80 +36,144 @@ type SendOnlyNode interface { // It only supports sending transactions // It must a http(s) url type sendOnlyNode struct { - uri url.URL - rpc *rpc.Client - geth *ethclient.Client - log logger.Logger - dialed bool - name string + uri url.URL + rpc *rpc.Client + geth *ethclient.Client + log logger.Logger + dialed bool + name string + chainID *big.Int + + chStop chan struct{} } -func NewSendOnlyNode(lggr logger.Logger, httpuri url.URL, name string) SendOnlyNode { +// NewSendOnlyNode returns a new sendonly node +func NewSendOnlyNode(lggr logger.Logger, httpuri url.URL, name string, chainID *big.Int) SendOnlyNode { s := new(sendOnlyNode) s.name = name s.log = lggr.Named("SendOnlyNode").Named(name).With( "nodeTier", "sendonly", ) s.uri = httpuri + s.chainID = chainID + s.chStop = make(chan struct{}) return s } -func (s *sendOnlyNode) Dial(_ context.Context) error { +// Start setups up and verifies the sendonly node +// Should only be called once in a node's lifecycle +// TODO: Failures to dial should put it into a retry loop +// https://app.shortcut.com/chainlinklabs/story/28182/eth-node-failover-consider-introducing-a-state-for-sendonly-nodes +func (s *sendOnlyNode) Start(startCtx context.Context) error { s.log.Debugw("evmclient.Client#Dial(...)") if s.dialed { panic("evmclient.Client.Dial(...) should only be called once during the node's lifetime.") } - uri := s.uri.String() - rpc, err := rpc.DialHTTP(uri) + rpc, err := rpc.DialHTTP(s.uri.String()) if err != nil { - return errors.Wrapf(err, "failed to dial secondary client: %v", uri) + return errors.Wrapf(err, "failed to dial secondary client: %v", s.uri.Redacted()) } s.dialed = true s.rpc = rpc s.geth = ethclient.NewClient(rpc) + + if id, err := s.getChainID(startCtx); err != nil { + s.log.Warn("sendonly rpc ChainID verification skipped", "err", err) + } else if id.Cmp(s.chainID) != 0 { + return errors.Errorf( + "sendonly rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", + id.String(), + s.chainID.String(), + s.name, + ) + } + return nil } -func (s sendOnlyNode) SendTransaction(ctx context.Context, tx *types.Transaction) error { - s.log.Debugw("evmclient.Client#SendTransaction(...)", - "tx", tx, +func (s *sendOnlyNode) Close() { + close(s.chStop) +} + +func (s *sendOnlyNode) logTiming(lggr logger.Logger, duration time.Duration, err error, callName string) { + promEVMPoolRPCCallTiming. + WithLabelValues( + s.chainID.String(), // chain id + s.name, // node name + s.uri.Host, // rpc domain + "true", // is send only + strconv.FormatBool(err == nil), // is successful + callName, // rpc call name + ). + Observe(float64(duration)) + lggr.Debugw(fmt.Sprintf("SendOnly RPC call: evmclient.#%s", callName), + "duration", duration, + "rpcDomain", s.uri.Host, + "name", s.name, + "chainID", s.chainID, + "sendOnly", false, + "err", err, ) +} + +func (s *sendOnlyNode) SendTransaction(parentCtx context.Context, tx *types.Transaction) (err error) { + defer func(start time.Time) { + s.logTiming(s.log, time.Since(start), err, "SendTransaction") + }(time.Now()) + + ctx, cancel := s.makeQueryCtx(parentCtx) + defer cancel() return s.wrap(s.geth.SendTransaction(ctx, tx)) } -func (s sendOnlyNode) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { - s.log.Debugw("evmclient.Client#BatchCall(...)", - "nBatchElems", len(b), - ) +func (s *sendOnlyNode) BatchCallContext(parentCtx context.Context, b []rpc.BatchElem) (err error) { + defer func(start time.Time) { + s.logTiming(s.log.With("nBatchElems", len(b)), time.Since(start), err, "BatchCallContext") + }(time.Now()) + + ctx, cancel := s.makeQueryCtx(parentCtx) + defer cancel() return s.wrap(s.rpc.BatchCallContext(ctx, b)) } -func (s sendOnlyNode) ChainID(ctx context.Context) (chainID *big.Int, err error) { - s.log.Debugw("evmclient.Client#ChainID(...)") - chainID, err = s.geth.ChainID(ctx) - err = s.wrap(err) - return +func (s *sendOnlyNode) ChainID() (chainID *big.Int) { + return s.chainID } -func (s sendOnlyNode) wrap(err error) error { - return wrap(err, fmt.Sprintf("sendonly http (%s)", s.uri.String())) +func (s *sendOnlyNode) wrap(err error) error { + return wrap(err, fmt.Sprintf("sendonly http (%s)", s.uri.Redacted())) } -func (s sendOnlyNode) String() string { - return fmt.Sprintf("(secondary)%s:%s", s.name, s.uri.String()) +func (s *sendOnlyNode) String() string { + return fmt.Sprintf("(secondary)%s:%s", s.name, s.uri.Redacted()) } -func (s sendOnlyNode) Verify(ctx context.Context, expectedChainID *big.Int) (err error) { - if chainID, err := s.ChainID(ctx); err != nil { - return errors.Wrap(err, "failed to verify chain ID") - } else if chainID.Cmp(expectedChainID) != 0 { - return errors.Errorf( - "sendonly rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", - chainID.String(), - expectedChainID.String(), - s.name, - ) +// getChainID returns the chainID and converts zero/empty values to errors. +func (s *sendOnlyNode) getChainID(parentCtx context.Context) (*big.Int, error) { + ctx, cancel := s.makeQueryCtx(parentCtx) + defer cancel() + + chainID, err := s.geth.ChainID(ctx) + if err != nil { + return nil, err + } else if chainID.Cmp(big.NewInt(0)) == 0 { + return nil, errors.New("zero/empty value") } - return nil + return chainID, nil +} + +// makeQueryCtx returns a context that cancels if: +// 1. Passed in ctx cancels +// 2. chStop is closed +// 3. Default timeout is reached (queryTimeout) +func (s *sendOnlyNode) makeQueryCtx(ctx context.Context) (context.Context, context.CancelFunc) { + var chCancel, timeoutCancel context.CancelFunc + ctx, chCancel = utils.WithCloseChan(ctx, s.chStop) + ctx, timeoutCancel = context.WithTimeout(ctx, queryTimeout) + cancel := func() { + chCancel() + timeoutCancel() + } + return ctx, cancel } diff --git a/core/chains/evm/client/simulated_backend.go b/core/chains/evm/client/simulated_backend.go index 34e235945f8..c62a3e07d8c 100644 --- a/core/chains/evm/client/simulated_backend.go +++ b/core/chains/evm/client/simulated_backend.go @@ -400,7 +400,15 @@ func (c *SimulatedBackendClient) BatchCallContext(ctx context.Context, b []rpc.B return nil } +// BatchCallContextAll makes a batch rpc call. +func (c *SimulatedBackendClient) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + return c.BatchCallContext(ctx, b) +} + // SuggestGasTipCap suggests a gas tip cap. func (c *SimulatedBackendClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { return nil, nil } + +// NodeStates implements evmclient.Client +func (c *SimulatedBackendClient) NodeStates() map[int32]string { return nil } diff --git a/core/chains/evm/config/chain_specific_config.go b/core/chains/evm/config/chain_specific_config.go index c413830d11f..55978c5358d 100644 --- a/core/chains/evm/config/chain_specific_config.go +++ b/core/chains/evm/config/chain_specific_config.go @@ -14,7 +14,7 @@ var ( DefaultGasFeeCap = assets.GWei(100) DefaultGasLimit uint64 = 500000 DefaultGasPrice = assets.GWei(20) - DefaultGasTip = assets.GWei(0) + DefaultGasTip = big.NewInt(1) // go-ethereum requires the tip to be at least 1 wei DefaultMinimumContractPayment = assets.NewLinkFromJuels(10000000000000) // 0.00001 LINK ) @@ -60,8 +60,12 @@ type ( minIncomingConfirmations uint32 minRequiredOutgoingConfirmations uint64 minimumContractPayment *assets.Link - nonceAutoSync bool - rpcDefaultBatchSize uint32 + nodeDeadAfterNoNewHeadersThreshold time.Duration + nodePollFailureThreshold uint32 + nodePollInterval time.Duration + + nonceAutoSync bool + rpcDefaultBatchSize uint32 // set true if fully configured complete bool @@ -99,7 +103,7 @@ func setChainSpecificConfigDefaultSets() { blockEmissionIdleWarningThreshold: 1 * time.Minute, blockHistoryEstimatorBatchSize: 4, // FIXME: Workaround `websocket: read limit exceeded` until https://app.clubhouse.io/chainlinklabs/story/6717/geth-websockets-can-sometimes-go-bad-under-heavy-load-proposal-for-eth-node-balancer blockHistoryEstimatorBlockDelay: 1, - blockHistoryEstimatorBlockHistorySize: 16, + blockHistoryEstimatorBlockHistorySize: 8, blockHistoryEstimatorTransactionPercentile: 60, chainType: "", eip1559DynamicFees: false, @@ -118,7 +122,7 @@ func setChainSpecificConfigDefaultSets() { gasLimitTransfer: 21000, gasPriceDefault: *DefaultGasPrice, gasTipCapDefault: *DefaultGasTip, - gasTipCapMinimum: *big.NewInt(0), + gasTipCapMinimum: *big.NewInt(1), headTrackerHistoryDepth: 100, headTrackerMaxBufferSize: 3, headTrackerSamplingInterval: 1 * time.Second, @@ -131,6 +135,9 @@ func setChainSpecificConfigDefaultSets() { minIncomingConfirmations: 3, minRequiredOutgoingConfirmations: 12, minimumContractPayment: DefaultMinimumContractPayment, + nodeDeadAfterNoNewHeadersThreshold: 3 * time.Minute, + nodePollFailureThreshold: 5, + nodePollInterval: 10 * time.Second, nonceAutoSync: true, ocrContractConfirmations: 4, ocrContractTransmitterTransmitTimeout: 10 * time.Second, @@ -141,9 +148,11 @@ func setChainSpecificConfigDefaultSets() { } mainnet := fallbackDefaultSet + mainnet.blockHistoryEstimatorBlockHistorySize = 4 // EIP-1559 does well on a smaller block history size + mainnet.blockHistoryEstimatorTransactionPercentile = 50 + mainnet.eip1559DynamicFees = true // enable EIP-1559 on Eth Mainnet and all testnets mainnet.linkContractAddress = "0x514910771AF9Ca656af840dff83E8264EcF986CA" mainnet.minimumContractPayment = assets.NewLinkFromJuels(100000000000000000) // 0.1 LINK - mainnet.blockHistoryEstimatorBlockHistorySize = 12 // mainnet has longer block times than everything else, so ideally this is kept small to keep it responsive // NOTE: There are probably other variables we can tweak for Kovan and other // test chains, but the defaults have been working fine and if it ain't // broke, don't fix it. @@ -151,10 +160,13 @@ func setChainSpecificConfigDefaultSets() { ropsten.linkContractAddress = "0x20fe562d797a42dcb3399062ae9546cd06f63280" kovan := mainnet kovan.linkContractAddress = "0xa36085F69e2889c224210F603D836748e7dC0088" + kovan.eip1559DynamicFees = false // FIXME: Kovan has strange behaviour with EIP1559, see: https://app.shortcut.com/chainlinklabs/story/34098/kovan-can-emit-blocks-that-violate-assumptions-in-block-history-estimator goerli := mainnet goerli.linkContractAddress = "0x326c977e6efc84e512bb9c30f76e30c160ed06fb" + goerli.eip1559DynamicFees = false // TODO: EIP1559 on goerli has not been adequately tested, see: https://app.shortcut.com/chainlinklabs/story/34098/kovan-can-emit-blocks-that-violate-assumptions-in-block-history-estimator rinkeby := mainnet rinkeby.linkContractAddress = "0x01BE23585060835E02B77ef475b0Cc51aA1e0709" + rinkeby.eip1559DynamicFees = false // TODO: EIP1559 on rinkeby has not been adequately tested, see: https://app.shortcut.com/chainlinklabs/story/34098/kovan-can-emit-blocks-that-violate-assumptions-in-block-history-estimator // xDai currently uses AuRa (like Parity) consensus so finality rules will be similar to parity // See: https://www.poa.network/for-users/whitepaper/poadao-v1/proof-of-authority diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 1f5fef6cdab..929b632afe0 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/chains" + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/config/envvar" @@ -26,6 +27,8 @@ import ( ) type ChainScopedOnlyConfig interface { + evmclient.NodeConfig + BalanceMonitorEnabled() bool BlockEmissionIdleWarningThreshold() time.Duration BlockHistoryEstimatorBatchSize() (size uint32) @@ -68,6 +71,7 @@ type ChainScopedOnlyConfig interface { MinIncomingConfirmations() uint32 MinRequiredOutgoingConfirmations() uint64 MinimumContractPayment() *assets.Link + NodeNoNewHeadsThreshold() time.Duration // OCR2 chain specific config OCR2ContractConfirmations() uint16 @@ -161,6 +165,9 @@ func (c *chainScopedConfig) validate() (err error) { if uint32(c.EvmGasBumpTxDepth()) > c.EvmMaxInFlightTransactions() { err = multierr.Combine(err, errors.New("ETH_GAS_BUMP_TX_DEPTH must be less than or equal to ETH_MAX_IN_FLIGHT_TRANSACTIONS")) } + if c.EvmGasTipCapDefault().Cmp(c.EvmGasTipCapMinimum()) < 0 { + err = multierr.Combine(err, errors.Errorf("EVM_GAS_TIP_CAP_DEFAULT (%s) must be greater than or equal to EVM_GAS_TIP_CAP_MINIMUM (%s)", c.EvmGasTipCapDefault(), c.EvmGasTipCapMinimum())) + } if c.EvmGasFeeCapDefault().Cmp(c.EvmGasTipCapDefault()) < 0 { err = multierr.Combine(err, errors.Errorf("EVM_GAS_FEE_CAP_DEFAULT (%s) must be greater than or equal to EVM_GAS_TIP_CAP_DEFAULT (%s)", c.EvmGasFeeCapDefault(), c.EvmGasTipCapDefault())) } @@ -1069,6 +1076,41 @@ func (c *chainScopedConfig) EvmGasTipCapMinimum() *big.Int { return &c.defaultSet.gasTipCapMinimum } +// NodeNoNewHeadsThreshold controls how long to wait after receiving no new +// heads before marking the node as out-of-sync +// Set to zero to disable out-of-sync checking +func (c *chainScopedConfig) NodeNoNewHeadsThreshold() time.Duration { + val, ok := c.GeneralConfig.GlobalNodeNoNewHeadsThreshold() + if ok { + c.logEnvOverrideOnce("NodeNoNewHeadsThreshold", val) + return val + } + return c.defaultSet.nodeDeadAfterNoNewHeadersThreshold +} + +// NodePollFailureThreshold indicates how many consecutive polls must fail in +// order to mark a node as unreachable. +// Set to zero to disable poll checking. +func (c *chainScopedConfig) NodePollFailureThreshold() uint32 { + val, ok := c.GeneralConfig.GlobalNodePollFailureThreshold() + if ok { + c.logEnvOverrideOnce("NodePollFailureThreshold", val) + return val + } + return c.defaultSet.nodePollFailureThreshold +} + +// NodePollInterval controls how often to poll the node to check for liveness. +// Set to zero to disable poll checking. +func (c *chainScopedConfig) NodePollInterval() time.Duration { + val, ok := c.GeneralConfig.GlobalNodePollInterval() + if ok { + c.logEnvOverrideOnce("NodePollInterval", val) + return val + } + return c.defaultSet.nodePollInterval +} + func (c *chainScopedConfig) lookupEnv(k string, parse func(string) (interface{}, error)) (interface{}, bool) { s, ok := os.LookupEnv(k) if !ok { diff --git a/core/chains/evm/config/mocks/chain_scoped_config.go b/core/chains/evm/config/mocks/chain_scoped_config.go index dbea7fd6c16..a3978fbdb54 100644 --- a/core/chains/evm/config/mocks/chain_scoped_config.go +++ b/core/chains/evm/config/mocks/chain_scoped_config.go @@ -831,6 +831,20 @@ func (_m *ChainScopedConfig) EthereumHTTPURL() *url.URL { return r0 } +// EthereumNodes provides a mock function with given fields: +func (_m *ChainScopedConfig) EthereumNodes() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // EthereumSecondaryURLs provides a mock function with given fields: func (_m *ChainScopedConfig) EthereumSecondaryURLs() []url.URL { ret := _m.Called() @@ -2272,6 +2286,69 @@ func (_m *ChainScopedConfig) GlobalMinimumContractPayment() (*assets.Link, bool) return r0, r1 } +// GlobalNodeNoNewHeadsThreshold provides a mock function with given fields: +func (_m *ChainScopedConfig) GlobalNodeNoNewHeadsThreshold() (time.Duration, bool) { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GlobalNodePollFailureThreshold provides a mock function with given fields: +func (_m *ChainScopedConfig) GlobalNodePollFailureThreshold() (uint32, bool) { + ret := _m.Called() + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GlobalNodePollInterval provides a mock function with given fields: +func (_m *ChainScopedConfig) GlobalNodePollInterval() (time.Duration, bool) { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // GlobalOCRContractConfirmations provides a mock function with given fields: func (_m *ChainScopedConfig) GlobalOCRContractConfirmations() (uint16, bool) { ret := _m.Called() @@ -2468,6 +2545,20 @@ func (_m *ChainScopedConfig) JobPipelineResultWriteQueueDepth() uint64 { return r0 } +// KeeperBaseFeeBufferPercent provides a mock function with given fields: +func (_m *ChainScopedConfig) KeeperBaseFeeBufferPercent() uint32 { + ret := _m.Called() + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + // KeeperCheckUpkeepGasPriceFeatureEnabled provides a mock function with given fields: func (_m *ChainScopedConfig) KeeperCheckUpkeepGasPriceFeatureEnabled() bool { ret := _m.Called() @@ -2680,36 +2771,64 @@ func (_m *ChainScopedConfig) LogFileDir() string { return r0 } -// LogLevel provides a mock function with given fields: -func (_m *ChainScopedConfig) LogLevel() zapcore.Level { +// LogFileMaxAge provides a mock function with given fields: +func (_m *ChainScopedConfig) LogFileMaxAge() int64 { ret := _m.Called() - var r0 zapcore.Level - if rf, ok := ret.Get(0).(func() zapcore.Level); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() } else { - r0 = ret.Get(0).(zapcore.Level) + r0 = ret.Get(0).(int64) } return r0 } -// LogSQL provides a mock function with given fields: -func (_m *ChainScopedConfig) LogSQL() bool { +// LogFileMaxBackups provides a mock function with given fields: +func (_m *ChainScopedConfig) LogFileMaxBackups() int64 { ret := _m.Called() - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() } else { - r0 = ret.Get(0).(bool) + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// LogFileMaxSize provides a mock function with given fields: +func (_m *ChainScopedConfig) LogFileMaxSize() utils.FileSize { + ret := _m.Called() + + var r0 utils.FileSize + if rf, ok := ret.Get(0).(func() utils.FileSize); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(utils.FileSize) + } + + return r0 +} + +// LogLevel provides a mock function with given fields: +func (_m *ChainScopedConfig) LogLevel() zapcore.Level { + ret := _m.Called() + + var r0 zapcore.Level + if rf, ok := ret.Get(0).(func() zapcore.Level); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(zapcore.Level) } return r0 } -// LogToDisk provides a mock function with given fields: -func (_m *ChainScopedConfig) LogToDisk() bool { +// LogSQL provides a mock function with given fields: +func (_m *ChainScopedConfig) LogSQL() bool { ret := _m.Called() var r0 bool @@ -2794,6 +2913,48 @@ func (_m *ChainScopedConfig) MinimumContractPayment() *assets.Link { return r0 } +// NodeNoNewHeadsThreshold provides a mock function with given fields: +func (_m *ChainScopedConfig) NodeNoNewHeadsThreshold() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// NodePollFailureThreshold provides a mock function with given fields: +func (_m *ChainScopedConfig) NodePollFailureThreshold() uint32 { + ret := _m.Called() + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + +// NodePollInterval provides a mock function with given fields: +func (_m *ChainScopedConfig) NodePollInterval() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + // OCR2BlockchainTimeout provides a mock function with given fields: func (_m *ChainScopedConfig) OCR2BlockchainTimeout() time.Duration { ret := _m.Called() @@ -3942,6 +4103,20 @@ func (_m *ChainScopedConfig) TelemetryIngressSendInterval() time.Duration { return r0 } +// TelemetryIngressSendTimeout provides a mock function with given fields: +func (_m *ChainScopedConfig) TelemetryIngressSendTimeout() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + // TelemetryIngressServerPubKey provides a mock function with given fields: func (_m *ChainScopedConfig) TelemetryIngressServerPubKey() string { ret := _m.Called() @@ -3972,6 +4147,20 @@ func (_m *ChainScopedConfig) TelemetryIngressURL() *url.URL { return r0 } +// TelemetryIngressUniConn provides a mock function with given fields: +func (_m *ChainScopedConfig) TelemetryIngressUniConn() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // TelemetryIngressUseBatchSend provides a mock function with given fields: func (_m *ChainScopedConfig) TelemetryIngressUseBatchSend() bool { ret := _m.Called() diff --git a/core/chains/evm/forwarders/forwarder.go b/core/chains/evm/forwarders/forwarder.go new file mode 100644 index 00000000000..077ea9b146c --- /dev/null +++ b/core/chains/evm/forwarders/forwarder.go @@ -0,0 +1,17 @@ +package forwarders + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// Forwarder is the struct for Forwarder Addresses +type Forwarder struct { + ID int64 + Address common.Address + EVMChainID utils.Big + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/core/chains/evm/forwarders/mocks/ORM.go b/core/chains/evm/forwarders/mocks/ORM.go new file mode 100644 index 00000000000..e13d934fe3f --- /dev/null +++ b/core/chains/evm/forwarders/mocks/ORM.go @@ -0,0 +1,81 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + common "github.com/ethereum/go-ethereum/common" + forwarders "github.com/smartcontractkit/chainlink/core/chains/evm/forwarders" + mock "github.com/stretchr/testify/mock" + + utils "github.com/smartcontractkit/chainlink/core/utils" +) + +// ORM is an autogenerated mock type for the ORM type +type ORM struct { + mock.Mock +} + +// CreateForwarder provides a mock function with given fields: addr, evmChainId +func (_m *ORM) CreateForwarder(addr common.Address, evmChainId utils.Big) (forwarders.Forwarder, error) { + ret := _m.Called(addr, evmChainId) + + var r0 forwarders.Forwarder + if rf, ok := ret.Get(0).(func(common.Address, utils.Big) forwarders.Forwarder); ok { + r0 = rf(addr, evmChainId) + } else { + r0 = ret.Get(0).(forwarders.Forwarder) + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address, utils.Big) error); ok { + r1 = rf(addr, evmChainId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteForwarder provides a mock function with given fields: id +func (_m *ORM) DeleteForwarder(id int32) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(int32) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FindForwarders provides a mock function with given fields: offset, limit +func (_m *ORM) FindForwarders(offset int, limit int) ([]forwarders.Forwarder, int, error) { + ret := _m.Called(offset, limit) + + var r0 []forwarders.Forwarder + if rf, ok := ret.Get(0).(func(int, int) []forwarders.Forwarder); ok { + r0 = rf(offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]forwarders.Forwarder) + } + } + + var r1 int + if rf, ok := ret.Get(1).(func(int, int) int); ok { + r1 = rf(offset, limit) + } else { + r1 = ret.Get(1).(int) + } + + var r2 error + if rf, ok := ret.Get(2).(func(int, int) error); ok { + r2 = rf(offset, limit) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} diff --git a/core/chains/evm/forwarders/orm.go b/core/chains/evm/forwarders/orm.go new file mode 100644 index 00000000000..b7b3cf5aac6 --- /dev/null +++ b/core/chains/evm/forwarders/orm.go @@ -0,0 +1,68 @@ +package forwarders + +import ( + "database/sql" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/sqlx" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/pg" + "github.com/smartcontractkit/chainlink/core/utils" +) + +//go:generate mockery --name ORM --output ./mocks/ --case=underscore + +type ORM interface { + CreateForwarder(addr common.Address, evmChainId utils.Big) (fwd Forwarder, err error) + FindForwarders(offset, limit int) ([]Forwarder, int, error) + DeleteForwarder(id int32) error +} + +type orm struct { + q pg.Q +} + +var _ ORM = (*orm)(nil) + +func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig) *orm { + return &orm{pg.NewQ(db, lggr, cfg)} +} + +// CreateForwarder creates the Forwarder address associated with the current EVM chain id. +func (o *orm) CreateForwarder(addr common.Address, evmChainId utils.Big) (fwd Forwarder, err error) { + sql := `INSERT INTO evm_forwarders (address, evm_chain_id, created_at, updated_at) VALUES ($1, $2, now(), now()) RETURNING *` + err = o.q.Get(&fwd, sql, addr, evmChainId) + return fwd, err +} + +// DeleteForwarder removes a forwarder address. +func (o *orm) DeleteForwarder(id int32) error { + q := `DELETE FROM evm_forwarders WHERE id = $1` + result, err := o.q.Exec(q, id) + if err != nil { + return err + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return sql.ErrNoRows + } + return nil +} + +// FindForwarders returns all forwarder addresses from offset up until limit. +func (o *orm) FindForwarders(offset, limit int) (fwds []Forwarder, count int, err error) { + sql := `SELECT count(*) FROM evm_forwarders` + if err = o.q.Get(&count, sql); err != nil { + return + } + + sql = `SELECT * FROM evm_forwarders ORDER BY created_at DESC, id DESC LIMIT $1 OFFSET $2` + if err = o.q.Select(&fwds, sql, limit, offset); err != nil { + return + } + return +} diff --git a/core/chains/evm/gas/block_history_estimator.go b/core/chains/evm/gas/block_history_estimator.go index f0eb5bee153..7354c524c49 100644 --- a/core/chains/evm/gas/block_history_estimator.go +++ b/core/chains/evm/gas/block_history_estimator.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" "sort" + "strings" "sync" "time" @@ -20,15 +21,10 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -const ( - // maxStartTime is the maximum amount of time we are allowed to spend - // trying to fill initial data on start. This must be capped because it can - // block the application from starting. - maxStartTime = 10 * time.Second - // maxEthNodeRequestTime is the worst case time we will wait for a response - // from the eth node before we consider it to be an error - maxEthNodeRequestTime = 30 * time.Second -) +// MaxStartTime is the maximum amount of time we are allowed to spend +// trying to fill initial data on start. This must be capped because it can +// block the application from starting. +var MaxStartTime = 10 * time.Second var ( promBlockHistoryEstimatorAllGasPricePercentiles = promauto.NewGaugeVec(prometheus.GaugeOpts{ @@ -86,7 +82,7 @@ type ( latestBaseFee *big.Int mu sync.RWMutex - logger logger.Logger + logger logger.SugaredLogger } ) @@ -109,7 +105,7 @@ func NewBlockHistoryEstimator(lggr logger.Logger, ethClient evmclient.Client, cf nil, nil, sync.RWMutex{}, - lggr.Named("BlockHistoryEstimator"), + logger.Sugared(lggr.Named("BlockHistoryEstimator")), } return b @@ -141,13 +137,15 @@ func (b *BlockHistoryEstimator) getCurrentBaseFee() *big.Int { return b.latestBaseFee } -func (b *BlockHistoryEstimator) Start() error { +// Start starts BlockHistoryEstimator service. +// The provided context can be used to terminate Start sequence. +func (b *BlockHistoryEstimator) Start(ctx context.Context) error { return b.StartOnce("BlockHistoryEstimator", func() error { b.logger.Trace("Starting") - ctx, cancel := context.WithTimeout(b.ctx, maxStartTime) + fetchCtx, cancel := context.WithTimeout(ctx, MaxStartTime) defer cancel() - latestHead, err := b.ethClient.HeadByNumber(ctx, nil) + latestHead, err := b.ethClient.HeadByNumber(fetchCtx, nil) if err != nil { b.logger.Warnw("Initial check for latest head failed", "err", err) } else if latestHead == nil { @@ -155,10 +153,17 @@ func (b *BlockHistoryEstimator) Start() error { } else { b.logger.Debugw("Got latest head", "number", latestHead.Number, "blockHash", latestHead.Hash.Hex()) b.setLatestBaseFee(latestHead.BaseFeePerGas) - b.FetchBlocksAndRecalculate(ctx, latestHead) + b.FetchBlocksAndRecalculate(fetchCtx, latestHead) + } + + // NOTE: This only checks the start context, not the fetch context + if ctx.Err() != nil { + return errors.Wrap(ctx.Err(), "failed to start BlockHistoryEstimator due to main context error") } + b.wg.Add(1) go b.runLoop() + b.logger.Trace("Started") return nil }) @@ -291,9 +296,6 @@ func (b *BlockHistoryEstimator) runLoop() { // FetchBlocksAndRecalculate fetches block history leading up to head and recalculates gas price. func (b *BlockHistoryEstimator) FetchBlocksAndRecalculate(ctx context.Context, head *evmtypes.Head) { - ctx, cancel := context.WithTimeout(ctx, maxEthNodeRequestTime) - defer cancel() - if err := b.FetchBlocks(ctx, head); err != nil { b.logger.Warnw("Error fetching blocks", "head", head, "err", err) return @@ -317,7 +319,7 @@ func (b *BlockHistoryEstimator) Recalculate(head *evmtypes.Head) { percentileGasPrice, percentileTipCap, err := b.percentilePrices(percentile, enableEIP1559) if err != nil { - if err == ErrNoSuitableTransactions { + if errors.Is(err, ErrNoSuitableTransactions) { lggr.Debug("No suitable transactions, skipping") } else { lggr.Warnw("Cannot calculate percentile prices", "err", err) @@ -398,7 +400,9 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes. } var reqs []rpc.BatchElem - for i := lowestBlockToFetch; i <= highestBlockToFetch; i++ { + // Fetch blocks in reverse order so if it times out halfway through we bias + // towards more recent blocks + for i := highestBlockToFetch; i >= lowestBlockToFetch; i-- { // NOTE: To save rpc calls, don't fetch blocks we already have in the history if _, exists := blocks[i]; exists { continue @@ -419,10 +423,18 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes. return err } - for i, req := range reqs { + for _, req := range reqs { result, err := req.Result, req.Error if err != nil { - lggr.Warnw("Error while fetching block", "err", err, "blockNum", int(lowestBlockToFetch)+i, "headNum", head.Number) + if strings.Contains(err.Error(), "failed to decode block number while unmarshalling block") { + lggr.Errorw( + fmt.Sprintf("Failed to fetch block: RPC node returned an empty block on query for block number %d even though the WS subscription already sent us this block. It might help to increase BLOCK_HISTORY_ESTIMATOR_BLOCK_DELAY (currently %d)", + HexToInt64(req.Args[0]), blockDelay, + ), + "err", err, "blockNum", HexToInt64(req.Args[0]), "headNum", head.Number) + } else { + lggr.Warnw("Failed to fetch block", "err", err, "blockNum", HexToInt64(req.Args[0]), "headNum", head.Number) + } continue } @@ -434,7 +446,7 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes. return errors.New("invariant violation: got nil block") } if block.Hash == (common.Hash{}) { - lggr.Warnw("Block was missing hash", "block", b, "blockNum", head.Number, "erroredBlockNum", block.Number) + lggr.Warnw("Block was missing hash", "block", b, "headNum", head.Number, "blockNum", block.Number) continue } @@ -475,7 +487,19 @@ func (b *BlockHistoryEstimator) batchFetch(ctx context.Context, reqs []rpc.Batch b.logger.Tracew(fmt.Sprintf("Batch fetching blocks %v thru %v", HexToInt64(reqs[i].Args[0]), HexToInt64(reqs[j-1].Args[0]))) - if err := b.ethClient.BatchCallContext(ctx, reqs[i:j]); err != nil { + err := b.ethClient.BatchCallContext(ctx, reqs[i:j]) + if errors.Is(err, context.DeadlineExceeded) { + // We ran out of time, return what we have + b.logger.Warnf("Batch fetching timed out; loaded %d/%d results", i, len(reqs)) + for k := i; k < len(reqs); k++ { + if k < j { + reqs[k].Error = errors.Wrap(err, "request failed") + } else { + reqs[k].Error = errors.Wrap(err, "request skipped; previous request exceeded deadline") + } + } + return nil + } else if err != nil { return errors.Wrap(err, "BlockHistoryEstimator#fetchBlocks error fetching blocks with BatchCallContext") } } @@ -610,12 +634,12 @@ func (b *BlockHistoryEstimator) EffectiveGasPrice(block Block, tx Transaction) * } if tx.MaxFeePerGas.Cmp(block.BaseFeePerGas) < 0 { // This should not pass config validation - b.logger.Errorw("AssumptionViolation: MaxFeePerGas >= BaseFeePerGas", "block", block, "tx", tx) + b.logger.AssumptionViolationw("MaxFeePerGas >= BaseFeePerGas", "block", block, "tx", tx) return nil } if tx.MaxFeePerGas.Cmp(tx.MaxPriorityFeePerGas) < 0 { // This should not pass config validation - b.logger.Errorw("AssumptionViolation: MaxFeePerGas >= MaxPriorityFeePerGas", "block", block, "tx", tx) + b.logger.AssumptionViolationw("MaxFeePerGas >= MaxPriorityFeePerGas", "block", block, "tx", tx) return nil } if tx.GasPrice != nil { @@ -651,7 +675,7 @@ func (b *BlockHistoryEstimator) EffectiveTipCap(block Block, tx Transaction) *bi } effectiveTipCap := big.NewInt(0).Sub(tx.GasPrice, block.BaseFeePerGas) if effectiveTipCap.Cmp(big.NewInt(0)) < 0 { - b.logger.Errorw("AssumptionViolation: GasPrice - BaseFeePerGas >= 0", "block", block, "tx", tx) + b.logger.AssumptionViolationw("GasPrice - BaseFeePerGas >= 0", "block", block, "tx", tx) return nil } return effectiveTipCap diff --git a/core/chains/evm/gas/block_history_estimator_test.go b/core/chains/evm/gas/block_history_estimator_test.go index e1250e45be8..31c73658e20 100644 --- a/core/chains/evm/gas/block_history_estimator_test.go +++ b/core/chains/evm/gas/block_history_estimator_test.go @@ -3,7 +3,6 @@ package gas_test import ( "context" "encoding/json" - "errors" "fmt" "math" "math/big" @@ -11,21 +10,23 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/assets" + "github.com/smartcontractkit/chainlink/core/chains" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" "github.com/smartcontractkit/chainlink/core/chains/evm/gas" gumocks "github.com/smartcontractkit/chainlink/core/chains/evm/gas/mocks" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/utils" ) func newConfigWithEIP1559DynamicFeesEnabled(t *testing.T) *gumocks.Config { @@ -81,8 +82,8 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h, nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == "0x29" && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && - b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == "0x2a" && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == "0x2a" && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && + b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == "0x29" && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &gas.Block{ @@ -93,9 +94,9 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { Number: 41, Hash: utils.NewHash(), } - }) + }).Once() - err := bhe.Start() + err := bhe.Start(testutils.Context(t)) require.NoError(t, err) assert.Len(t, bhe.RollingBlockHistory(), 2) @@ -108,6 +109,51 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { config.AssertExpectations(t) }) + t.Run("starts and loads partial history if fetch context times out", func(t *testing.T) { + cfg := newConfigWithEIP1559DynamicFeesEnabled(t) + + cfg.On("BlockHistoryEstimatorBatchSize").Return(uint32(1)) + cfg.On("BlockHistoryEstimatorBlockDelay").Return(blockDelay) + cfg.On("BlockHistoryEstimatorBlockHistorySize").Return(historySize) + cfg.On("BlockHistoryEstimatorTransactionPercentile").Maybe().Return(percentile) + cfg.On("EvmGasLimitMultiplier").Maybe().Return(float32(1)) + cfg.On("EvmMinGasPriceWei").Maybe().Return(minGasPrice) + cfg.On("EvmEIP1559DynamicFees").Maybe().Return(true) + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + bhe := newBlockHistoryEstimator(t, ethClient, cfg) + + h := &evmtypes.Head{Hash: utils.NewHash(), Number: 42, BaseFeePerGas: utils.NewBigI(420)} + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h, nil) + // First succeeds (42) + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(42) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + elems[0].Result = &gas.Block{ + Number: 42, + Hash: utils.NewHash(), + } + }).Once() + // Second fails (41) + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(41) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) + })).Return(errors.Wrap(context.DeadlineExceeded, "some error message")).Once() + + err := bhe.Start(testutils.Context(t)) + require.NoError(t, err) + + require.Len(t, bhe.RollingBlockHistory(), 1) + assert.Equal(t, int(bhe.RollingBlockHistory()[0].Number), 42) + + assert.Equal(t, big.NewInt(420), gas.GetLatestBaseFee(bhe)) + + ethClient.AssertExpectations(t) + config.AssertExpectations(t) + }) + t.Run("boots even if initial batch call returns nothing", func(t *testing.T) { ethClient := cltest.NewEthClientMockWithDefaultChain(t) @@ -119,7 +165,7 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { return len(b) == int(historySize) })).Return(nil) - err := bhe.Start() + err := bhe.Start(testutils.Context(t)) require.NoError(t, err) // non-eip1559 block @@ -136,7 +182,7 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(nil, errors.New("something exploded")) - err := bhe.Start() + err := bhe.Start(testutils.Context(t)) require.NoError(t, err) assert.Nil(t, gas.GetLatestBaseFee(bhe)) @@ -162,7 +208,54 @@ func TestBlockHistoryEstimator_Start(t *testing.T) { ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h, nil) ethClient.On("BatchCallContext", mock.Anything, mock.Anything).Return(errors.New("something went wrong")) - err := bhe.Start() + err := bhe.Start(testutils.Context(t)) + require.NoError(t, err) + + assert.Equal(t, big.NewInt(420), gas.GetLatestBaseFee(bhe)) + + _, _, err = bhe.GetLegacyGas(make([]byte, 0), 100) + require.Error(t, err) + require.Contains(t, err.Error(), "has not finished the first gas estimation yet") + + _, _, err = bhe.GetDynamicFee(100) + require.Error(t, err) + require.Contains(t, err.Error(), "has not finished the first gas estimation yet") + + ethClient.AssertExpectations(t) + config.AssertExpectations(t) + }) + + t.Run("returns error if main context is cancelled", func(t *testing.T) { + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + bhe := newBlockHistoryEstimator(t, ethClient, config) + + h := &evmtypes.Head{Hash: utils.NewHash(), Number: 42, BaseFeePerGas: utils.NewBigI(420)} + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h, nil) + ethClient.On("BatchCallContext", mock.Anything, mock.Anything).Return(errors.New("this error doesn't matter")) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := bhe.Start(ctx) + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + + ethClient.AssertExpectations(t) + config.AssertExpectations(t) + }) + + t.Run("starts anyway even if the fetch context is cancelled due to taking longer than the MaxStartTime", func(t *testing.T) { + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + bhe := newBlockHistoryEstimator(t, ethClient, config) + + h := &evmtypes.Head{Hash: utils.NewHash(), Number: 42, BaseFeePerGas: utils.NewBigI(420)} + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h, nil) + ethClient.On("BatchCallContext", mock.Anything, mock.Anything).Return(errors.New("this error doesn't matter")).Run(func(_ mock.Arguments) { + time.Sleep(gas.MaxStartTime + 1*time.Second) + }) + + err := bhe.Start(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, big.NewInt(420), gas.GetLatestBaseFee(bhe)) @@ -291,25 +384,26 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(41) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(43) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(42) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) })).Once().Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b41 // This errored block will be ignored + elems[0].Result = &b43 + // This errored block (42) will be ignored elems[1].Error = errors.New("something went wrong") }) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 1 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(43) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(41) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) })).Once().Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b43 + elems[0].Result = &b41 }) err := bhe.FetchBlocks(context.Background(), cltest.Head(43)) require.NoError(t, err) - assert.Len(t, bhe.RollingBlockHistory(), 2) + require.Len(t, bhe.RollingBlockHistory(), 2) assert.Equal(t, 41, int(bhe.RollingBlockHistory()[0].Number)) // 42 is missing because the fetch errored assert.Equal(t, 43, int(bhe.RollingBlockHistory()[1].Number)) @@ -330,12 +424,12 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { // 43 is skipped because it was already in the history ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(42) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && - b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(44) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(44) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && + b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(42) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) })).Once().Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b42 - elems[1].Result = &b44 + elems[0].Result = &b44 + elems[1].Result = &b42 }) head := evmtypes.NewHead(big.NewInt(44), b44.Hash, b43.Hash, uint64(time.Now().Unix()), utils.NewBig(&cltest.FixtureChainID)) @@ -394,12 +488,12 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(2) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && - b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(3) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(3) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && + b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(2) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) })).Once().Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b2 - elems[1].Result = &b3 + elems[0].Result = &b3 + elems[1].Result = &b2 }) head2 := evmtypes.NewHead(big.NewInt(2), b2.Hash, b1.Hash, uint64(time.Now().Unix()), utils.NewBig(&cltest.FixtureChainID)) @@ -460,16 +554,16 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(2) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && - b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(3) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == gas.Int64ToHex(3) && b[0].Args[1] == true && reflect.TypeOf(b[0].Result) == reflect.TypeOf(&gas.Block{}) && + b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == gas.Int64ToHex(2) && b[1].Args[1] == true && reflect.TypeOf(b[1].Result) == reflect.TypeOf(&gas.Block{}) })).Once().Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) b2New := b2 b2New.Hash = head2.Hash - elems[0].Result = &b2New + elems[1].Result = &b2New b3New := b3 b3New.Hash = head3.Hash - elems[1].Result = &b3New + elems[0].Result = &b3New }) err := bhe.FetchBlocks(context.Background(), &head3) @@ -579,14 +673,14 @@ func TestBlockHistoryEstimator_FetchBlocksAndRecalculate_NoEIP1559(t *testing.T) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 3 && - b[0].Args[0] == "0x1" && + b[0].Args[0] == "0x3" && b[1].Args[0] == "0x2" && - b[2].Args[0] == "0x3" + b[2].Args[0] == "0x1" })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b1 + elems[0].Result = &b3 elems[1].Result = &b2 - elems[2].Result = &b3 + elems[2].Result = &b1 }) bhe.FetchBlocksAndRecalculate(context.Background(), cltest.Head(3)) @@ -1063,6 +1157,40 @@ func TestBlockHistoryEstimator_Recalculate_EIP1559(t *testing.T) { config.AssertExpectations(t) }) + t.Run("respects minimum gas tip cap", func(t *testing.T) { + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + config := newConfigWithEIP1559DynamicFeesEnabled(t) + + config.On("EvmMaxGasPriceWei").Return(maxGasPrice) + config.On("EvmMinGasPriceWei").Return(big.NewInt(0)) + config.On("EvmGasTipCapMinimum").Return(big.NewInt(1)) + config.On("BlockHistoryEstimatorTransactionPercentile").Return(uint16(35)) + + bhe := newBlockHistoryEstimator(t, ethClient, config) + + b1Hash := utils.NewHash() + + blocks := []gas.Block{ + gas.Block{ + BaseFeePerGas: big.NewInt(10), + Number: 0, + Hash: b1Hash, + ParentHash: common.Hash{}, + Transactions: cltest.DynamicFeeTransactionsFromTipCaps(0, 0, 0, 0, 100), + }, + } + + gas.SetRollingBlockHistory(bhe, blocks) + + bhe.Recalculate(cltest.Head(0)) + + price := gas.GetTipCap(bhe) + assert.Equal(t, big.NewInt(1), price) + + ethClient.AssertExpectations(t) + config.AssertExpectations(t) + }) + t.Run("allows to set zero tip cap if minimum allows it", func(t *testing.T) { // Because everyone loves *cheap* gas! ethClient := cltest.NewEthClientMockWithDefaultChain(t) diff --git a/core/chains/evm/gas/fixed_price_estimator.go b/core/chains/evm/gas/fixed_price_estimator.go index baa4c4043a6..e119a6bb8ec 100644 --- a/core/chains/evm/gas/fixed_price_estimator.go +++ b/core/chains/evm/gas/fixed_price_estimator.go @@ -14,16 +14,16 @@ var _ Estimator = &fixedPriceEstimator{} type fixedPriceEstimator struct { config Config - lggr logger.Logger + lggr logger.SugaredLogger } // NewFixedPriceEstimator returns a new "FixedPrice" estimator which will // always use the config default values for gas prices and limits func NewFixedPriceEstimator(cfg Config, lggr logger.Logger) Estimator { - return &fixedPriceEstimator{cfg, lggr.Named("FixedPriceEstimator")} + return &fixedPriceEstimator{cfg, logger.Sugared(lggr.Named("FixedPriceEstimator"))} } -func (f *fixedPriceEstimator) Start() error { return nil } +func (f *fixedPriceEstimator) Start(context.Context) error { return nil } func (f *fixedPriceEstimator) Close() error { return nil } func (f *fixedPriceEstimator) OnNewLongestChain(_ context.Context, _ *evmtypes.Head) {} diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go index 67ab82fdcce..5d60d81490f 100644 --- a/core/chains/evm/gas/helpers_test.go +++ b/core/chains/evm/gas/helpers_test.go @@ -1,6 +1,14 @@ package gas -import "math/big" +import ( + "math/big" + "time" +) + +func init() { + // No need to wait 10 seconds in tests + MaxStartTime = 1 * time.Second +} func BlockHistoryEstimatorFromInterface(bhe Estimator) *BlockHistoryEstimator { return bhe.(*BlockHistoryEstimator) diff --git a/core/chains/evm/gas/mocks/estimator.go b/core/chains/evm/gas/mocks/estimator.go index eb83f638d6a..29c5fddee0e 100644 --- a/core/chains/evm/gas/mocks/estimator.go +++ b/core/chains/evm/gas/mocks/estimator.go @@ -159,13 +159,13 @@ func (_m *Estimator) OnNewLongestChain(_a0 context.Context, _a1 *types.Head) { _m.Called(_a0, _a1) } -// Start provides a mock function with given fields: -func (_m *Estimator) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Estimator) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index bf19cb205f3..cf2f1b862a6 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -77,7 +77,7 @@ type DynamicFee struct { //go:generate mockery --name Estimator --output ./mocks/ --case=underscore type Estimator interface { OnNewLongestChain(context.Context, *evmtypes.Head) - Start() error + Start(context.Context) error Close() error GetLegacyGas(calldata []byte, gasLimit uint64, opts ...Opt) (gasPrice *big.Int, chainSpecificGasLimit uint64, err error) BumpLegacyGas(originalGasPrice *big.Int, gasLimit uint64) (bumpedGasPrice *big.Int, chainSpecificGasLimit uint64, err error) @@ -268,7 +268,7 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { } // BumpLegacyGasPriceOnly will increase the price and apply multiplier to the gas limit -func BumpLegacyGasPriceOnly(cfg Config, lggr logger.Logger, currentGasPrice, originalGasPrice *big.Int, originalGasLimit uint64) (gasPrice *big.Int, chainSpecificGasLimit uint64, err error) { +func BumpLegacyGasPriceOnly(cfg Config, lggr logger.SugaredLogger, currentGasPrice, originalGasPrice *big.Int, originalGasLimit uint64) (gasPrice *big.Int, chainSpecificGasLimit uint64, err error) { gasPrice, err = bumpGasPrice(cfg, lggr, currentGasPrice, originalGasPrice) if err != nil { return nil, 0, err @@ -281,7 +281,7 @@ func BumpLegacyGasPriceOnly(cfg Config, lggr logger.Logger, currentGasPrice, ori // - A configured percentage bump (ETH_GAS_BUMP_PERCENT) on top of the baseline price. // - A configured fixed amount of Wei (ETH_GAS_PRICE_WEI) on top of the baseline price. // The baseline price is the maximum of the previous gas price attempt and the node's current gas price. -func bumpGasPrice(cfg Config, lggr logger.Logger, currentGasPrice, originalGasPrice *big.Int) (*big.Int, error) { +func bumpGasPrice(cfg Config, lggr logger.SugaredLogger, currentGasPrice, originalGasPrice *big.Int) (*big.Int, error) { maxGasPrice := cfg.EvmMaxGasPriceWei() var priceByPercentage = new(big.Int) @@ -296,7 +296,7 @@ func bumpGasPrice(cfg Config, lggr logger.Logger, currentGasPrice, originalGasPr if currentGasPrice.Cmp(maxGasPrice) > 0 { // Shouldn't happen because the estimator should not be allowed to // estimate a higher gas than the maximum allowed - lggr.Errorf("AssumptionViolation: Ignoring current gas price of %s that would exceed max gas price of %s", currentGasPrice.String(), maxGasPrice.String()) + lggr.AssumptionViolationf("Ignoring current gas price of %s that would exceed max gas price of %s", currentGasPrice.String(), maxGasPrice.String()) } else if bumpedGasPrice.Cmp(currentGasPrice) < 0 { // If the current gas price is higher than the old price bumped, use that instead bumpedGasPrice = currentGasPrice diff --git a/core/chains/evm/gas/optimism_estimator.go b/core/chains/evm/gas/optimism_estimator.go index af8f18331da..e8cf326efd1 100644 --- a/core/chains/evm/gas/optimism_estimator.go +++ b/core/chains/evm/gas/optimism_estimator.go @@ -63,7 +63,7 @@ func NewOptimismEstimator(lggr logger.Logger, config Config, client optimismRPCC } } -func (o *optimismEstimator) Start() error { +func (o *optimismEstimator) Start(context.Context) error { return o.StartOnce("OptimismEstimator", func() error { go o.run() <-o.chInitialised @@ -244,7 +244,7 @@ func NewOptimism2Estimator(lggr logger.Logger, config Config, client optimismRPC } } -func (o *optimism2Estimator) Start() error { +func (o *optimism2Estimator) Start(context.Context) error { return o.StartOnce("Optimism2Estimator", func() error { go o.run() <-o.chInitialised diff --git a/core/chains/evm/gas/optimism_estimator_test.go b/core/chains/evm/gas/optimism_estimator_test.go index df7bdb7d3cd..34e1f84dc03 100644 --- a/core/chains/evm/gas/optimism_estimator_test.go +++ b/core/chains/evm/gas/optimism_estimator_test.go @@ -7,12 +7,14 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/chains/evm/gas" - "github.com/smartcontractkit/chainlink/core/chains/evm/gas/mocks" - "github.com/smartcontractkit/chainlink/core/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/core/chains/evm/gas/mocks" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/logger" ) func Test_OptimismEstimator(t *testing.T) { @@ -37,7 +39,7 @@ func Test_OptimismEstimator(t *testing.T) { res.L2GasPrice = big.NewInt(142) }) - require.NoError(t, o.Start()) + require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(calldata, gasLimit) require.NoError(t, err) @@ -57,7 +59,7 @@ func Test_OptimismEstimator(t *testing.T) { client.On("Call", mock.Anything, "rollup_gasPrices").Return(errors.New("kaboom")) - require.NoError(t, o.Start()) + require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, o.Close()) }) _, _, err := o.GetLegacyGas(calldata, gasLimit) @@ -96,7 +98,7 @@ func Test_Optimism2Estimator(t *testing.T) { (*big.Int)(res).SetInt64(42) }) - require.NoError(t, o.Start()) + require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(calldata, gasLimit) require.NoError(t, err) @@ -116,7 +118,7 @@ func Test_Optimism2Estimator(t *testing.T) { client.On("Call", mock.Anything, "eth_gasPrice").Return(errors.New("kaboom")) - require.NoError(t, o.Start()) + require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, o.Close()) }) _, _, err := o.GetLegacyGas(calldata, gasLimit) diff --git a/core/chains/evm/headtracker/head_broadcaster.go b/core/chains/evm/headtracker/head_broadcaster.go index fdc14ad1b2c..be65766b88b 100644 --- a/core/chains/evm/headtracker/head_broadcaster.go +++ b/core/chains/evm/headtracker/head_broadcaster.go @@ -50,7 +50,7 @@ type headBroadcaster struct { lastCallbackID int } -func (hb *headBroadcaster) Start() error { +func (hb *headBroadcaster) Start(context.Context) error { return hb.StartOnce("HeadBroadcaster", func() error { hb.wgDone.Add(1) go hb.run() @@ -132,13 +132,16 @@ func (hb *headBroadcaster) executeCallbacks() { wg := sync.WaitGroup{} wg.Add(len(callbacks)) + ctx, cancel := utils.ContextFromChan(hb.chClose) + defer cancel() + for _, callback := range callbacks { go func(trackable httypes.HeadTrackable) { defer wg.Done() start := time.Now() - ctx, cancel := context.WithTimeout(context.Background(), TrackableCallbackTimeout) + cctx, cancel := context.WithTimeout(ctx, TrackableCallbackTimeout) defer cancel() - trackable.OnNewLongestChain(ctx, head) + trackable.OnNewLongestChain(cctx, head) elapsed := time.Since(start) hb.logger.Debugw(fmt.Sprintf("Finished callback in %s", elapsed), "callbackType", reflect.TypeOf(trackable), "blockNumber", head.Number, "time", elapsed) diff --git a/core/chains/evm/headtracker/head_broadcaster_test.go b/core/chains/evm/headtracker/head_broadcaster_test.go index 16069a66a54..f88b4092ea1 100644 --- a/core/chains/evm/headtracker/head_broadcaster_test.go +++ b/core/chains/evm/headtracker/head_broadcaster_test.go @@ -14,6 +14,7 @@ import ( evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" @@ -52,8 +53,8 @@ func TestHeadBroadcaster_Subscribe(t *testing.T) { orm := headtracker.NewORM(db, logger, cfg, *ethClient.ChainID()) hs := headtracker.NewHeadSaver(logger, orm, evmCfg) ht := headtracker.NewHeadTracker(logger, ethClient, evmCfg, hr, hs) - require.NoError(t, hr.Start()) - require.NoError(t, ht.Start()) + require.NoError(t, hr.Start(testutils.Context(t))) + require.NoError(t, ht.Start(testutils.Context(t))) latest1, unsubscribe1 := hr.Subscribe(checker1) // "latest head" is nil here because we didn't receive any yet @@ -85,7 +86,7 @@ func TestHeadBroadcaster_BroadcastNewLongestChain(t *testing.T) { lggr := logger.TestLogger(t) broadcaster := headtracker.NewHeadBroadcaster(lggr) - err := broadcaster.Start() + err := broadcaster.Start(testutils.Context(t)) require.NoError(t, err) // no subscribers - shall do nothing @@ -123,7 +124,7 @@ func TestHeadBroadcaster_TrackableCallbackTimeout(t *testing.T) { lggr := logger.TestLogger(t) broadcaster := headtracker.NewHeadBroadcaster(lggr) - err := broadcaster.Start() + err := broadcaster.Start(testutils.Context(t)) require.NoError(t, err) slowAwaiter := cltest.NewAwaiter() diff --git a/core/chains/evm/headtracker/head_listener.go b/core/chains/evm/headtracker/head_listener.go index e5b4fa0210f..633cdb727ce 100644 --- a/core/chains/evm/headtracker/head_listener.go +++ b/core/chains/evm/headtracker/head_listener.go @@ -6,7 +6,6 @@ import ( "time" "github.com/ethereum/go-ethereum" - "github.com/jpillora/backoff" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -116,9 +115,11 @@ func (hl *headListener) receiveHeaders(ctx context.Context, handleNewHead httype } case err, open := <-hl.headSubscription.Err(): - if open && err != nil { - return err + // err can be nil, because of using chainIDSubForwarder + if !open || err == nil { + return errors.New("head listener: subscription Err channel prematurely closed") } + return err case <-t.C: // We haven't received a head on the channel for a long time, log a warning @@ -129,10 +130,7 @@ func (hl *headListener) receiveHeaders(ctx context.Context, handleNewHead httype } func (hl *headListener) subscribe(ctx context.Context) bool { - subscribeRetryBackoff := backoff.Backoff{ - Min: 1 * time.Second, - Max: 10 * time.Second, - } + subscribeRetryBackoff := utils.NewRedialBackoff() chainID := hl.ethClient.ChainID().String() diff --git a/core/chains/evm/headtracker/head_listener_test.go b/core/chains/evm/headtracker/head_listener_test.go index a2bec0fcc05..677a1573cac 100644 --- a/core/chains/evm/headtracker/head_listener_test.go +++ b/core/chains/evm/headtracker/head_listener_test.go @@ -141,87 +141,105 @@ func Test_HeadListener_NotReceivingHeads(t *testing.T) { doneAwaiter.AwaitOrFail(t) } -func Test_HeadListener_ResubscribesIfWSClosed(t *testing.T) { - l := logger.TestLogger(t) - ethClient := cltest.NewEthClientMockWithDefaultChain(t) - cfg := cltest.NewTestGeneralConfig(t) - evmcfg := evmtest.NewChainScopedConfig(t, cfg) - chStop := make(chan struct{}) - hl := headtracker.NewHeadListener(l, ethClient, evmcfg, chStop) - - hnhCalled := make(chan *evmtypes.Head) - hnh := func(ctx context.Context, header *evmtypes.Head) error { - hnhCalled <- header - return nil - } - doneAwaiter := cltest.NewAwaiter() - done := func() { - doneAwaiter.ItHappened() +func Test_HeadListener_SubscriptionErr(t *testing.T) { + tests := []struct { + name string + err error + closeErr bool + }{ + {"nil error", nil, false}, + {"socket error", errors.New("close 1006 (abnormal closure): unexpected EOF"), false}, + {"close Err channel", nil, true}, } - chSubErrTest := make(chan error) - var chSubErr <-chan error = chSubErrTest - sub := new(evmmocks.Subscription) - // sub.Err is called twice because we enter the select loop two times: once - // initially and once again after exactly one head has been received - sub.On("Err").Return(chSubErr).Twice() - - subscribeAwaiter := cltest.NewAwaiter() - var headsCh chan<- *evmtypes.Head - // Initial subscribe - ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub, nil).Once().Run(func(args mock.Arguments) { - headsCh = args.Get(1).(chan<- *evmtypes.Head) - subscribeAwaiter.ItHappened() - }) - go func() { - hl.ListenForNewHeads(hnh, done) - }() - - // Put a head on the channel to ensure we test all code paths - subscribeAwaiter.AwaitOrFail(t) - head := cltest.Head(0) - headsCh <- head - - h := <-hnhCalled - assert.Equal(t, head, h) - - // Expect a call to unsubscribe on error - sub.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { - close(headsCh) - // geth guarantees that Unsubscribe closes the errors channel - close(chSubErrTest) - }) - // Expect a resubscribe - chSubErrTest2 := make(chan error) - var chSubErr2 <-chan error = chSubErrTest2 - sub2 := new(evmmocks.Subscription) - sub2.On("Err").Return(chSubErr2) - subscribeAwaiter2 := cltest.NewAwaiter() - - var headsCh2 chan<- *evmtypes.Head - ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub2, nil).Once().Run(func(args mock.Arguments) { - headsCh2 = args.Get(1).(chan<- *evmtypes.Head) - subscribeAwaiter2.ItHappened() - }) - - // Simulate websocket error/close - chSubErrTest <- errors.New("close 1006 (abnormal closure): unexpected EOF") - - // Wait for it to resubscribe - subscribeAwaiter2.AwaitOrFail(t) - - head2 := cltest.Head(1) - headsCh2 <- head2 - - h2 := <-hnhCalled - assert.Equal(t, head2, h2) - - // Second call to unsubscribe on close - sub2.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { - close(headsCh2) - // geth guarantees that Unsubscribe closes the errors channel - close(chSubErrTest2) - }) - close(chStop) - doneAwaiter.AwaitOrFail(t) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + l := logger.TestLogger(t) + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + cfg := cltest.NewTestGeneralConfig(t) + evmcfg := evmtest.NewChainScopedConfig(t, cfg) + chStop := make(chan struct{}) + hl := headtracker.NewHeadListener(l, ethClient, evmcfg, chStop) + + hnhCalled := make(chan *evmtypes.Head) + hnh := func(ctx context.Context, header *evmtypes.Head) error { + hnhCalled <- header + return nil + } + doneAwaiter := cltest.NewAwaiter() + done := doneAwaiter.ItHappened + + chSubErrTest := make(chan error) + var chSubErr <-chan error = chSubErrTest + sub := new(evmmocks.Subscription) + // sub.Err is called twice because we enter the select loop two times: once + // initially and once again after exactly one head has been received + sub.On("Err").Return(chSubErr).Twice() + + subscribeAwaiter := cltest.NewAwaiter() + var headsCh chan<- *evmtypes.Head + // Initial subscribe + ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub, nil).Once().Run(func(args mock.Arguments) { + headsCh = args.Get(1).(chan<- *evmtypes.Head) + subscribeAwaiter.ItHappened() + }) + go func() { + hl.ListenForNewHeads(hnh, done) + }() + + // Put a head on the channel to ensure we test all code paths + subscribeAwaiter.AwaitOrFail(t) + head := cltest.Head(0) + headsCh <- head + + h := <-hnhCalled + assert.Equal(t, head, h) + + // Expect a call to unsubscribe on error + sub.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { + close(headsCh) + // geth guarantees that Unsubscribe closes the errors channel + if !test.closeErr { + close(chSubErrTest) + } + }) + // Expect a resubscribe + chSubErrTest2 := make(chan error) + var chSubErr2 <-chan error = chSubErrTest2 + sub2 := new(evmmocks.Subscription) + sub2.On("Err").Return(chSubErr2) + subscribeAwaiter2 := cltest.NewAwaiter() + + var headsCh2 chan<- *evmtypes.Head + ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub2, nil).Once().Run(func(args mock.Arguments) { + headsCh2 = args.Get(1).(chan<- *evmtypes.Head) + subscribeAwaiter2.ItHappened() + }) + + // Sending test error + if test.closeErr { + close(chSubErrTest) + } else { + chSubErrTest <- test.err + } + + // Wait for it to resubscribe + subscribeAwaiter2.AwaitOrFail(t) + + head2 := cltest.Head(1) + headsCh2 <- head2 + + h2 := <-hnhCalled + assert.Equal(t, head2, h2) + + // Second call to unsubscribe on close + sub2.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { + close(headsCh2) + // geth guarantees that Unsubscribe closes the errors channel + close(chSubErrTest2) + }) + close(chStop) + doneAwaiter.AwaitOrFail(t) + }) + } } diff --git a/core/chains/evm/headtracker/head_saver_test.go b/core/chains/evm/headtracker/head_saver_test.go index bf48f271d04..18d28a29e64 100644 --- a/core/chains/evm/headtracker/head_saver_test.go +++ b/core/chains/evm/headtracker/head_saver_test.go @@ -1,7 +1,6 @@ package headtracker_test import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -10,6 +9,7 @@ import ( htmocks "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/mocks" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" ) @@ -33,10 +33,10 @@ func TestHeadSaver_Save(t *testing.T) { saver, _ := configureSaver(t) head := cltest.Head(1) - err := saver.Save(context.TODO(), head) + err := saver.Save(testutils.Context(t), head) require.NoError(t, err) - latest, err := saver.LatestHeadFromDB(context.TODO()) + latest, err := saver.LatestHeadFromDB(testutils.Context(t)) require.NoError(t, err) require.Equal(t, int64(1), latest.Number) @@ -55,11 +55,11 @@ func TestHeadSaver_LoadFromDB(t *testing.T) { saver, orm := configureSaver(t) for i := 0; i < 5; i++ { - err := orm.IdempotentInsertHead(context.TODO(), cltest.Head(i)) + err := orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(i)) require.NoError(t, err) } - latestHead, err := saver.LoadFromDB(context.TODO()) + latestHead, err := saver.LoadFromDB(testutils.Context(t)) require.NoError(t, err) require.NotNil(t, latestHead) require.Equal(t, int64(4), latestHead.Number) diff --git a/core/chains/evm/headtracker/head_tracker.go b/core/chains/evm/headtracker/head_tracker.go index f9bd3f21976..a69d613dc48 100644 --- a/core/chains/evm/headtracker/head_tracker.go +++ b/core/chains/evm/headtracker/head_tracker.go @@ -46,8 +46,6 @@ type headTracker struct { backfillMB *utils.Mailbox broadcastMB *utils.Mailbox headListener httypes.HeadListener - ctx context.Context - cancel context.CancelFunc chStop chan struct{} wgDone sync.WaitGroup utils.StartStopOnce @@ -63,7 +61,6 @@ func NewHeadTracker( ) httypes.HeadTracker { chStop := make(chan struct{}) lggr = lggr.Named(logger.HeadTracker) - ctx, cancel := context.WithCancel(context.Background()) return &headTracker{ headBroadcaster: headBroadcaster, ethClient: ethClient, @@ -72,8 +69,6 @@ func NewHeadTracker( log: lggr, backfillMB: utils.NewMailbox(1), broadcastMB: utils.NewMailbox(HeadsBufferSize), - ctx: ctx, - cancel: cancel, chStop: chStop, headListener: NewHeadListener(lggr, ethClient, config, chStop), headSaver: headSaver, @@ -85,10 +80,10 @@ func (ht *headTracker) SetLogLevel(lvl zapcore.Level) { } // Start starts HeadTracker service. -func (ht *headTracker) Start() error { +func (ht *headTracker) Start(ctx context.Context) error { return ht.StartOnce("HeadTracker", func() error { ht.log.Debugf("Starting HeadTracker with chain id: %v", ht.chainID.Int64()) - latestChain, err := ht.headSaver.LoadFromDB(ht.ctx) + latestChain, err := ht.headSaver.LoadFromDB(ctx) if err != nil { return err } @@ -107,11 +102,14 @@ func (ht *headTracker) Start() error { // anyway when we connect (but we should not rely on this because it is // not specced). If it happens this is fine, and the head will be // ignored as a duplicate. - initialHead, err := ht.getInitialHead(ht.ctx) + initialHead, err := ht.getInitialHead(ctx) if err != nil { + if errors.Is(err, ctx.Err()) { + return nil + } ht.log.Errorw("Error getting initial head", "err", err) } else if initialHead != nil { - if err := ht.handleNewHead(ht.ctx, initialHead); err != nil { + if err := ht.handleNewHead(ctx, initialHead); err != nil { return errors.Wrap(err, "error handling initial head") } } else { @@ -130,7 +128,6 @@ func (ht *headTracker) Start() error { // Close stops HeadTracker service. func (ht *headTracker) Close() error { return ht.StopOnce("HeadTracker", func() error { - ht.cancel() close(ht.chStop) ht.wgDone.Wait() return nil @@ -256,6 +253,9 @@ func (ht *headTracker) broadcastLoop() { func (ht *headTracker) backfillLoop() { defer ht.wgDone.Done() + ctx, cancel := utils.ContextFromChan(ht.chStop) + defer cancel() + for { select { case <-ht.chStop: @@ -268,10 +268,10 @@ func (ht *headTracker) backfillLoop() { } head := evmtypes.AsHead(item) { - err := ht.Backfill(ht.ctx, head, uint(ht.config.EvmFinalityDepth())) + err := ht.Backfill(ctx, head, uint(ht.config.EvmFinalityDepth())) if err != nil { ht.log.Warnw("Unexpected error while backfilling heads", "err", err) - } else if ht.ctx.Err() != nil { + } else if ctx.Err() != nil { break } } @@ -341,11 +341,11 @@ var NullTracker httypes.HeadTracker = &nullTracker{} type nullTracker struct{} -func (*nullTracker) Start() error { return nil } -func (*nullTracker) Close() error { return nil } -func (*nullTracker) Ready() error { return nil } -func (*nullTracker) Healthy() error { return nil } -func (*nullTracker) SetLogLevel(zapcore.Level) {} +func (*nullTracker) Start(context.Context) error { return nil } +func (*nullTracker) Close() error { return nil } +func (*nullTracker) Ready() error { return nil } +func (*nullTracker) Healthy() error { return nil } +func (*nullTracker) SetLogLevel(zapcore.Level) {} func (*nullTracker) Backfill(ctx context.Context, headWithChain *evmtypes.Head, depth uint) (err error) { return nil } diff --git a/core/chains/evm/headtracker/head_tracker_test.go b/core/chains/evm/headtracker/head_tracker_test.go index 21abc5e4c35..a56a3549f75 100644 --- a/core/chains/evm/headtracker/head_tracker_test.go +++ b/core/chains/evm/headtracker/head_tracker_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/ethereum/go-ethereum" gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" @@ -17,6 +19,8 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/sqlx" + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker" @@ -30,7 +34,6 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) func firstHead(t *testing.T, db *sqlx.DB) (h evmtypes.Head) { @@ -59,10 +62,10 @@ func TestHeadTracker_New(t *testing.T) { sub.On("Err").Return(nil) orm := headtracker.NewORM(db, logger, config, cltest.FixtureChainID) - assert.Nil(t, orm.IdempotentInsertHead(context.TODO(), cltest.Head(1))) + assert.Nil(t, orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(1))) last := cltest.Head(16) - assert.Nil(t, orm.IdempotentInsertHead(context.TODO(), last)) - assert.Nil(t, orm.IdempotentInsertHead(context.TODO(), cltest.Head(10))) + assert.Nil(t, orm.IdempotentInsertHead(testutils.Context(t), last)) + assert.Nil(t, orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(10))) evmcfg := newCfg(t) ht := createHeadTracker(t, ethClient, evmcfg, orm) @@ -83,19 +86,19 @@ func TestHeadTracker_Save_InsertsAndTrimsTable(t *testing.T) { orm := headtracker.NewORM(db, logger, config, cltest.FixtureChainID) for idx := 0; idx < 200; idx++ { - assert.Nil(t, orm.IdempotentInsertHead(context.TODO(), cltest.Head(idx))) + assert.Nil(t, orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(idx))) } ht := createHeadTracker(t, ethClient, config, orm) h := cltest.Head(200) - require.NoError(t, ht.headSaver.Save(context.TODO(), h)) + require.NoError(t, ht.headSaver.Save(testutils.Context(t), h)) assert.Equal(t, big.NewInt(200), ht.headSaver.LatestChain().ToInt()) firstHead := firstHead(t, db) assert.Equal(t, big.NewInt(101), firstHead.ToInt()) - lastHead, err := orm.LatestHead(context.TODO()) + lastHead, err := orm.LatestHead(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, int64(200), lastHead.Number) } @@ -141,14 +144,14 @@ func TestHeadTracker_Get(t *testing.T) { } if test.initial != nil { - assert.Nil(t, orm.IdempotentInsertHead(context.TODO(), test.initial)) + assert.Nil(t, orm.IdempotentInsertHead(testutils.Context(t), test.initial)) } ht := createHeadTracker(t, ethClient, config, orm) ht.Start(t) if test.toSave != nil { - err := ht.headSaver.Save(context.TODO(), test.toSave) + err := ht.headSaver.Save(testutils.Context(t), test.toSave) assert.NoError(t, err) } @@ -182,6 +185,41 @@ func TestHeadTracker_Start_NewHeads(t *testing.T) { ethClient.AssertExpectations(t) } +func TestHeadTracker_Start_CancelContext(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + logger := logger.TestLogger(t) + config := newCfg(t) + orm := headtracker.NewORM(db, logger, config, cltest.FixtureChainID) + ethClient, sub := cltest.NewEthClientAndSubMockWithDefaultChain(t) + sub.On("Err").Return(nil) + sub.On("Unsubscribe").Return(nil) + chStarted := make(chan struct{}) + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Run(func(args mock.Arguments) { + ctx := args.Get(0).(context.Context) + select { + case <-ctx.Done(): + return + case <-time.After(10 * time.Second): + assert.FailNow(t, "context was not cancelled within 10s") + } + }).Return(cltest.Head(0), nil) + ethClient.On("SubscribeNewHead", mock.Anything, mock.Anything). + Run(func(mock.Arguments) { close(chStarted) }). + Return(sub, nil) + + ht := createHeadTracker(t, ethClient, config, orm) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(1 * time.Second) + cancel() + }() + err := ht.headTracker.Start(ctx) + require.NoError(t, err) +} + func TestHeadTracker_CallsHeadTrackableCallbacks(t *testing.T) { t.Parallel() g := gomega.NewWithT(t) @@ -335,7 +373,7 @@ func TestHeadTracker_Start_LoadsLatestChain(t *testing.T) { })).Once().Return() ht.Start(t) - h, err := orm.LatestHead(context.TODO()) + h, err := orm.LatestHead(testutils.Context(t)) require.NoError(t, err) require.NotNil(t, h) assert.Equal(t, h.Number, int64(3)) @@ -701,7 +739,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -720,7 +758,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -745,7 +783,7 @@ func TestHeadTracker_Backfill(t *testing.T) { require.NotNil(t, h.Parent.Parent.Parent) assert.Equal(t, int64(9), h.Parent.Parent.Parent.Number) - writtenHead, err := orm.HeadByHash(context.TODO(), head10.Hash) + writtenHead, err := orm.HeadByHash(testutils.Context(t), head10.Hash) require.NoError(t, err) assert.Equal(t, int64(10), writtenHead.Number) @@ -758,7 +796,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -793,7 +831,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -821,7 +859,7 @@ func TestHeadTracker_Backfill(t *testing.T) { ethClient.On("HeadByNumber", mock.Anything, big.NewInt(0)). Return(&head0, nil) - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h1)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h1)) ht := createHeadTrackerWithNeverSleeper(t, ethClient, cfg, orm) @@ -843,7 +881,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -876,7 +914,7 @@ func TestHeadTracker_Backfill(t *testing.T) { logger := logger.TestLogger(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) for _, h := range heads { - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), &h)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &h)) } ethClient := cltest.NewEthClientMock(t) @@ -959,8 +997,9 @@ func (u *headTrackerUniverse) Backfill(ctx context.Context, head *evmtypes.Head, func (u *headTrackerUniverse) Start(t *testing.T) { u.mu.Lock() defer u.mu.Unlock() - require.NoError(t, u.headBroadcaster.Start()) - require.NoError(t, u.headTracker.Start()) + ctx := testutils.Context(t) + require.NoError(t, u.headBroadcaster.Start(ctx)) + require.NoError(t, u.headTracker.Start(ctx)) t.Cleanup(func() { u.Stop(t) }) diff --git a/core/chains/evm/headtracker/mocks/head_broadcaster.go b/core/chains/evm/headtracker/mocks/head_broadcaster.go index adcb62f5f1c..202d8084154 100644 --- a/core/chains/evm/headtracker/mocks/head_broadcaster.go +++ b/core/chains/evm/headtracker/mocks/head_broadcaster.go @@ -3,6 +3,8 @@ package mocks import ( + context "context" + headtrackertypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" mock "github.com/stretchr/testify/mock" @@ -61,13 +63,13 @@ func (_m *HeadBroadcaster) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *HeadBroadcaster) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *HeadBroadcaster) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/headtracker/orm.go b/core/chains/evm/headtracker/orm.go index 236770a17b4..099ab643fab 100644 --- a/core/chains/evm/headtracker/orm.go +++ b/core/chains/evm/headtracker/orm.go @@ -86,7 +86,7 @@ func (orm *orm) HeadByHash(ctx context.Context, hash common.Hash) (head *evmtype q := orm.q.WithOpts(pg.WithParentCtx(ctx)) head = new(evmtypes.Head) err = q.Get(head, `SELECT * FROM evm_heads WHERE evm_chain_id = $1 AND hash = $2`, orm.chainID, hash) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return head, err diff --git a/core/chains/evm/headtracker/orm_test.go b/core/chains/evm/headtracker/orm_test.go index c054558a4b0..b7d593cca91 100644 --- a/core/chains/evm/headtracker/orm_test.go +++ b/core/chains/evm/headtracker/orm_test.go @@ -1,9 +1,10 @@ package headtracker_test import ( - "context" "testing" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker" @@ -24,18 +25,18 @@ func TestORM_IdempotentInsertHead(t *testing.T) { // Returns nil when inserting first head head := cltest.Head(0) - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), head)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), head)) // Head is inserted - foundHead, err := orm.LatestHead(context.TODO()) + foundHead, err := orm.LatestHead(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, head.Hash, foundHead.Hash) // Returns nil when inserting same head again - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), head)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), head)) // Head is still inserted - foundHead, err = orm.LatestHead(context.TODO()) + foundHead, err = orm.LatestHead(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, head.Hash, foundHead.Hash) } @@ -50,13 +51,13 @@ func TestORM_TrimOldHeads(t *testing.T) { for i := 0; i < 10; i++ { head := cltest.Head(i) - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), head)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), head)) } - err := orm.TrimOldHeads(context.TODO(), 5) + err := orm.TrimOldHeads(testutils.Context(t), 5) require.NoError(t, err) - heads, err := orm.LatestHeads(context.TODO(), 10) + heads, err := orm.LatestHeads(testutils.Context(t), 10) require.NoError(t, err) require.Equal(t, 5, len(heads)) @@ -79,10 +80,10 @@ func TestORM_HeadByHash(t *testing.T) { if i == 5 { hash = head.Hash } - require.NoError(t, orm.IdempotentInsertHead(context.TODO(), head)) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), head)) } - head, err := orm.HeadByHash(context.TODO(), hash) + head, err := orm.HeadByHash(testutils.Context(t), hash) require.NoError(t, err) require.Equal(t, hash, head.Hash) require.Equal(t, int64(5), head.Number) @@ -97,7 +98,7 @@ func TestORM_HeadByHash_NotFound(t *testing.T) { orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) hash := cltest.Head(123).Hash - head, err := orm.HeadByHash(context.TODO(), hash) + head, err := orm.HeadByHash(testutils.Context(t), hash) require.Nil(t, head) require.NoError(t, err) @@ -111,7 +112,7 @@ func TestORM_LatestHeads_NoRows(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) orm := headtracker.NewORM(db, logger, cfg, cltest.FixtureChainID) - heads, err := orm.LatestHeads(context.TODO(), 100) + heads, err := orm.LatestHeads(testutils.Context(t), 100) require.Zero(t, len(heads)) require.NoError(t, err) diff --git a/core/chains/evm/headtracker/types/types.go b/core/chains/evm/headtracker/types/types.go index 710407a3a69..81e8d818dc4 100644 --- a/core/chains/evm/headtracker/types/types.go +++ b/core/chains/evm/headtracker/types/types.go @@ -28,7 +28,7 @@ type HeadSaver interface { // HeadTracker holds and stores the latest block number experienced by this particular node in a thread safe manner. // Reconstitutes the last block number from the data store on reboot. type HeadTracker interface { - services.Service + services.ServiceCtx // SetLogLevel changes log level for HeadTracker logger SetLogLevel(lvl zapcore.Level) // Backfill given a head will fill in any missing heads up to the given depth @@ -51,7 +51,7 @@ type HeadBroadcasterRegistry interface { // congestion than the head tracker, and missed heads should be expected by consuming jobs //go:generate mockery --name HeadBroadcaster --output ../mocks/ --case=underscore type HeadBroadcaster interface { - services.Service + services.ServiceCtx BroadcastNewLongestChain(head *evmtypes.Head) HeadBroadcasterRegistry } diff --git a/core/chains/evm/legacy.go b/core/chains/evm/legacy.go index a965e664b50..8c975b1d587 100644 --- a/core/chains/evm/legacy.go +++ b/core/chains/evm/legacy.go @@ -1,14 +1,18 @@ package evm import ( + "encoding/json" "fmt" "math/big" "net/url" + "sort" "github.com/pkg/errors" "gopkg.in/guregu/null.v4" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/sqlx" ) @@ -18,6 +22,8 @@ type LegacyEthNodeConfig interface { EthereumURL() string EthereumHTTPURL() *url.URL EthereumSecondaryURLs() []url.URL + EthereumNodes() string + LogSQL() bool } const missingEthChainIDMsg = `missing ETH_CHAIN_ID; this env var is required if ETH_URL is set @@ -55,6 +61,10 @@ For more information on configuring your node, check the docs: https://docs.chai ` func ClobberDBFromEnv(db *sqlx.DB, config LegacyEthNodeConfig, lggr logger.Logger) error { + if err := SetupMultiplePrimaries(db, config, lggr); err != nil { + return errors.Wrap(err, "failed to setup multiple primary nodes") + } + primaryWS := config.EthereumURL() if primaryWS == "" { return nil @@ -86,11 +96,56 @@ func ClobberDBFromEnv(db *sqlx.DB, config LegacyEthNodeConfig, lggr logger.Logge } for i, url := range config.EthereumSecondaryURLs() { + if config.EthereumHTTPURL() != nil && url.String() == config.EthereumHTTPURL().String() { + lggr.Errorf("Got secondary URL %s which is already specified as a primary node HTTP URL. It does not make any sense to have both primary and sendonly nodes sharing the same URL and does not grant any extra redundancy. This sendonly RPC url will be ignored.", url.String()) + continue + } name := fmt.Sprintf("sendonly-%d-%s", i, ethChainID) if _, err := db.Exec(stmt, name, ethChainID, nil, url.String(), true); err != nil { return errors.Wrapf(err, "failed to upsert %s", name) } } + return nil +} + +// SetupMultiplePrimaries is a hack/shim method to allow node operators to +// specify multiple nodes via ENV +// See: https://app.shortcut.com/chainlinklabs/epic/33587/overhaul-config?cf_workflow=500000005&ct_workflow=all +func SetupMultiplePrimaries(db *sqlx.DB, cfg LegacyEthNodeConfig, lggr logger.Logger) (err error) { + if cfg.EthereumNodes() == "" { + return nil + } + + lggr.Info("EVM_NODES was set; clobbering evm_nodes table") + _, err = db.Exec(`TRUNCATE evm_nodes;`) + if err != nil { + return errors.Wrap(err, "failed to truncate evm_nodes table while inserting nodes set by EVM_NODES") + } + + var nodes []evmtypes.Node + if err = json.Unmarshal([]byte(cfg.EthereumNodes()), &nodes); err != nil { + return errors.Wrapf(err, "invalid nodes json, got: %q", cfg.EthereumNodes()) + } + // Sorting gives a consistent insert ordering + sort.Slice(nodes, func(i, j int) bool { + return nodes[i].Name < nodes[j].Name + }) + + chainIDs := make(map[string]struct{}) + for _, n := range nodes { + chainIDs[n.EVMChainID.String()] = struct{}{} + } + for cid := range chainIDs { + if _, err := pg.NewQ(db, lggr, cfg).Exec("INSERT INTO evm_chains (id, created_at, updated_at) VALUES ($1, NOW(), NOW()) ON CONFLICT DO NOTHING;", cid); err != nil { + return errors.Wrapf(err, "failed to insert chain %s", cid) + } + } + + stmt := `INSERT INTO evm_nodes (name, evm_chain_id, ws_url, http_url, send_only, created_at, updated_at) + VALUES (:name, :evm_chain_id, :ws_url, :http_url, :send_only, now(), now()) + ON CONFLICT DO NOTHING;` + _, err = pg.NewQ(db, lggr, cfg).NamedExec(stmt, nodes) + return errors.Wrap(err, "failed to insert nodes") } diff --git a/core/chains/evm/legacy_test.go b/core/chains/evm/legacy_test.go index 275e4dc0724..c3893cd783e 100644 --- a/core/chains/evm/legacy_test.go +++ b/core/chains/evm/legacy_test.go @@ -7,10 +7,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" "github.com/smartcontractkit/chainlink/core/chains/evm" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" ) @@ -20,6 +22,7 @@ type legacyEthNodeConfig struct { ethereumURL string ethereumHTTPURL *url.URL ethereumSecondaryURLs []url.URL + evmNodes string } func (c legacyEthNodeConfig) DefaultChainID() *big.Int { @@ -38,50 +41,167 @@ func (c legacyEthNodeConfig) EthereumSecondaryURLs() []url.URL { return c.ethereumSecondaryURLs } +func (c legacyEthNodeConfig) EthereumNodes() string { + return c.evmNodes +} + +func (c legacyEthNodeConfig) LogSQL() bool { return false } + func Test_ClobberDBFromEnv(t *testing.T) { - db := pgtest.NewSqlxDB(t) var fixtureChains int64 = 2 var fixtureNodes int64 = 1 + t.Run("inserts nodes from config", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + + cfg := legacyEthNodeConfig{ + defaultChainID: big.NewInt(42), + ethereumURL: "ws://example.com/foo/ws", + ethereumHTTPURL: cltest.MustParseURL(t, "http://example.com/foo"), + ethereumSecondaryURLs: []url.URL{*cltest.MustParseURL(t, "http://secondary1.example/foo"), *cltest.MustParseURL(t, "https://secondary2.example/bar")}, + } + + err := evm.ClobberDBFromEnv(db, cfg, logger.TestLogger(t)) + require.NoError(t, err) + + cltest.AssertCount(t, db, "evm_chains", fixtureChains+1) + cltest.AssertCount(t, db, "evm_nodes", fixtureNodes+3) + + var primaryNode evmtypes.Node + err = db.Get(&primaryNode, `SELECT * FROM evm_nodes WHERE evm_chain_id = 42 AND NOT send_only`) + require.NoError(t, err) + + assert.Equal(t, "primary-0-42", primaryNode.Name) + assert.Equal(t, cfg.defaultChainID.String(), primaryNode.EVMChainID.String()) + assert.True(t, primaryNode.WSURL.Valid) + assert.Equal(t, cfg.ethereumURL, primaryNode.WSURL.String) + assert.True(t, primaryNode.HTTPURL.Valid) + assert.Equal(t, cfg.ethereumHTTPURL.String(), primaryNode.HTTPURL.String) + assert.False(t, primaryNode.SendOnly) + + var sendonlyNodes []evmtypes.Node + err = db.Select(&sendonlyNodes, `SELECT * FROM evm_nodes WHERE evm_chain_id = 42 AND send_only ORDER BY http_url`) + require.NoError(t, err) + require.Len(t, sendonlyNodes, 2) + + assert.True(t, sendonlyNodes[0].SendOnly) + assert.Equal(t, "sendonly-0-42", sendonlyNodes[0].Name) + assert.False(t, sendonlyNodes[0].WSURL.Valid) + assert.True(t, sendonlyNodes[0].HTTPURL.Valid) + assert.Equal(t, "http://secondary1.example/foo", sendonlyNodes[0].HTTPURL.String) + + assert.True(t, sendonlyNodes[1].SendOnly) + assert.Equal(t, "sendonly-1-42", sendonlyNodes[1].Name) + assert.False(t, sendonlyNodes[1].WSURL.Valid) + assert.True(t, sendonlyNodes[1].HTTPURL.Valid) + assert.Equal(t, "https://secondary2.example/bar", sendonlyNodes[1].HTTPURL.String) + }) + + t.Run("ignores sendonlys if they duplicate the primary URL", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + cfg := legacyEthNodeConfig{ + defaultChainID: big.NewInt(42), + ethereumURL: "ws://example.com/foo/ws", + ethereumHTTPURL: cltest.MustParseURL(t, "http://example.com/foo"), + ethereumSecondaryURLs: []url.URL{*cltest.MustParseURL(t, "http://example.com/foo"), *cltest.MustParseURL(t, "https://secondary2.example/bar")}, + } + + lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + err := evm.ClobberDBFromEnv(db, cfg, lggr) + require.NoError(t, err) + + testutils.RequireLogMessage(t, observedLogs, "Got secondary URL http://example.com/foo which is already specified as a primary node HTTP URL. It does not make any sense to have both primary and sendonly nodes sharing the same URL and does not grant any extra redundancy. This sendonly RPC url will be ignored.") + }) +} + +func Test_SetupMultiplePrimaries(t *testing.T) { + db := pgtest.NewSqlxDB(t) + + // Insert existing node which will be erased + pgtest.MustExec(t, db, `INSERT INTO evm_nodes (name, evm_chain_id, ws_url, http_url, send_only, created_at, updated_at) VALUES ('foo','0','ws://example.com',null,false,NOW(),NOW())`) + + s := ` +[ + { + "name": "primary_0_1", + "evmChainId": "0", + "wsUrl": "ws://test1.invalid", + "sendOnly": false + }, + { + "name": "primary_0_2", + "evmChainId": "0", + "wsUrl": "ws://test2.invalid", + "httpUrl": "https://test2.invalid", + "sendOnly": false + }, + { + "name": "primary_1337_1", + "evmChainId": "1337", + "wsUrl": "ws://test3.invalid", + "httpUrl": "http://test3.invalid", + "sendOnly": false + }, + { + "name": "sendonly_1337_1", + "evmChainId": "1337", + "httpUrl": "http://test4.invalid", + "sendOnly": true + }, + { + "name": "sendonly_0_1", + "evmChainId": "0", + "httpUrl": "http://test5.invalid", + "sendOnly": true + }, + { + "name": "primary_42_1", + "evmChainId": "42", + "wsUrl": "ws://test6.invalid", + "sendOnly": false + }, + { + "name": "sendonly_43_1", + "evmChainId": "43", + "httpUrl": "http://test7.invalid", + "sendOnly": true + }, + { + "name": "zzzz this will be ignored due to duplicate ws url", + "evmChainId": "0", + "wsUrl": "ws://test1.invalid", + "sendOnly": false + }, + { + "name": "zzzz this will be ignored due to duplicate http url", + "evmChainId": "0", + "wsUrl": "ws://test8.invalid", + "httpUrl": "https://test2.invalid", + "sendOnly": false + } +] + ` + cfg := legacyEthNodeConfig{ - defaultChainID: big.NewInt(42), - ethereumURL: "ws://example.com/foo/ws", - ethereumHTTPURL: cltest.MustParseURL(t, "http://example.com/foo"), - ethereumSecondaryURLs: []url.URL{*cltest.MustParseURL(t, "http://secondary1.example/foo"), *cltest.MustParseURL(t, "https://secondary2.example/bar")}, + evmNodes: s, } err := evm.ClobberDBFromEnv(db, cfg, logger.TestLogger(t)) require.NoError(t, err) - cltest.AssertCount(t, db, "evm_chains", fixtureChains+1) - cltest.AssertCount(t, db, "evm_nodes", fixtureNodes+3) + cltest.AssertCount(t, db, "evm_nodes", 7) - var primaryNode evmtypes.Node - err = db.Get(&primaryNode, `SELECT * FROM evm_nodes WHERE evm_chain_id = 42 AND NOT send_only`) + var nodes []evmtypes.Node + err = db.Select(&nodes, `SELECT * FROM evm_nodes ORDER BY name ASC`) require.NoError(t, err) - assert.Equal(t, "primary-0-42", primaryNode.Name) - assert.Equal(t, cfg.defaultChainID.String(), primaryNode.EVMChainID.String()) - assert.True(t, primaryNode.WSURL.Valid) - assert.Equal(t, cfg.ethereumURL, primaryNode.WSURL.String) - assert.True(t, primaryNode.HTTPURL.Valid) - assert.Equal(t, cfg.ethereumHTTPURL.String(), primaryNode.HTTPURL.String) - assert.False(t, primaryNode.SendOnly) + require.Len(t, nodes, 7) - var sendonlyNodes []evmtypes.Node - err = db.Select(&sendonlyNodes, `SELECT * FROM evm_nodes WHERE evm_chain_id = 42 AND send_only ORDER BY http_url`) - require.NoError(t, err) - require.Len(t, sendonlyNodes, 2) - - assert.True(t, sendonlyNodes[0].SendOnly) - assert.Equal(t, "sendonly-0-42", sendonlyNodes[0].Name) - assert.False(t, sendonlyNodes[0].WSURL.Valid) - assert.True(t, sendonlyNodes[0].HTTPURL.Valid) - assert.Equal(t, "http://secondary1.example/foo", sendonlyNodes[0].HTTPURL.String) - - assert.True(t, sendonlyNodes[1].SendOnly) - assert.Equal(t, "sendonly-1-42", sendonlyNodes[1].Name) - assert.False(t, sendonlyNodes[1].WSURL.Valid) - assert.True(t, sendonlyNodes[1].HTTPURL.Valid) - assert.Equal(t, "https://secondary2.example/bar", sendonlyNodes[1].HTTPURL.String) + assert.Equal(t, "primary_0_1", nodes[0].Name) + assert.Equal(t, "primary_0_2", nodes[1].Name) + assert.Equal(t, "primary_1337_1", nodes[2].Name) + assert.Equal(t, "primary_42_1", nodes[3].Name) + assert.Equal(t, "sendonly_0_1", nodes[4].Name) + assert.Equal(t, "sendonly_1337_1", nodes[5].Name) + assert.Equal(t, "sendonly_43_1", nodes[6].Name) } diff --git a/core/chains/evm/log/broadcaster.go b/core/chains/evm/log/broadcaster.go index 3e312cc67a7..81103684102 100644 --- a/core/chains/evm/log/broadcaster.go +++ b/core/chains/evm/log/broadcaster.go @@ -46,9 +46,13 @@ type ( // Of course, these backfilled logs + any new logs will only be sent after the NumConfirmations for given subscriber. Broadcaster interface { utils.DependentAwaiter - services.Service + services.ServiceCtx httypes.HeadTrackable - ReplayFromBlock(number int64) + + // ReplayFromBlock enqueues a replay from the provided block number. If forceBroadcast is + // set to true, the broadcaster will broadcast logs that were already marked consumed + // previously by any subscribers. + ReplayFromBlock(number int64, forceBroadcast bool) IsConnected() bool Register(listener Listener, opts ListenerOpts) (unsubscribe func()) @@ -76,6 +80,11 @@ type ( sub *subscriber } + replayRequest struct { + fromBlock int64 + forceBroadcast bool + } + broadcaster struct { orm ORM config Config @@ -100,7 +109,7 @@ type ( chStop chan struct{} wgDone sync.WaitGroup trackedAddressesCount atomic.Uint32 - replayChannel chan int64 + replayChannel chan replayRequest highestSavedHead *evmtypes.Head lastSeenHeadNumber atomic.Int64 logger logger.Logger @@ -165,11 +174,11 @@ func NewBroadcaster(orm ORM, ethClient evmclient.Client, config Config, lggr log DependentAwaiter: utils.NewDependentAwaiter(), chStop: chStop, highestSavedHead: highestSavedHead, - replayChannel: make(chan int64, 1), + replayChannel: make(chan replayRequest, 1), } } -func (b *broadcaster) Start() error { +func (b *broadcaster) Start(context.Context) error { return b.StartOnce("LogBroadcaster", func() error { b.wgDone.Add(2) go b.awaitInitialSubscribers() @@ -177,10 +186,14 @@ func (b *broadcaster) Start() error { }) } -func (b *broadcaster) ReplayFromBlock(number int64) { - b.logger.Infof("Replay requested from block number: %v", number) +// ReplayFromBlock implements the Broadcaster interface. +func (b *broadcaster) ReplayFromBlock(number int64, forceBroadcast bool) { + b.logger.Infow("Replay requested", "block number", number, "force", forceBroadcast) select { - case b.replayChannel <- number: + case b.replayChannel <- replayRequest{ + fromBlock: number, + forceBroadcast: forceBroadcast, + }: default: } } @@ -389,6 +402,14 @@ func (b *broadcaster) eventLoop(chRawLogs <-chan types.Log, chErr <-chan error) b.logger.Debug("Starting the event loop") for { + // Replay requests take priority. + select { + case req := <-b.replayChannel: + b.onReplayRequest(req) + return true, nil + default: + } + select { case rawLog := <-chRawLogs: b.logger.Debugw("Received a log", @@ -402,12 +423,8 @@ func (b *broadcaster) eventLoop(chRawLogs <-chan types.Log, chErr <-chan error) // The eth node connection was terminated so we need to backfill after resubscribing. lggr := b.logger // Do we have logs in the pool? - if min := b.logPool.heap.FindMin(); min != nil { - // They are are invalid, since we may have missed 'removed' logs. - b.logPool = newLogPool() - // Note: even if we crash right now, PendingMinBlock is preserved in the database and we will backfill the same. - blockNum := int64(min.(Uint64)) - b.backfillBlockNumber.SetValid(blockNum) + // They are are invalid, since we may have missed 'removed' logs. + if blockNum := b.invalidatePool(); blockNum > 0 { lggr = lggr.With("blockNumber", blockNum) } lggr.Debugw("Subscription terminated. Backfilling after resubscribing") @@ -416,12 +433,8 @@ func (b *broadcaster) eventLoop(chRawLogs <-chan types.Log, chErr <-chan error) case <-b.changeSubscriberStatus.Notify(): needsResubscribe = b.onChangeSubscriberStatus() || needsResubscribe - case blockNumber := <-b.replayChannel: - // NOTE: This ignores r.highestNumConfirmations, but it is - // generally assumed that this will only be performed rarely and - // manually by someone who knows what he is doing - b.backfillBlockNumber.SetValid(blockNumber) - b.logger.Debugw("Returning from the event loop to replay logs from specific block number", "blockNumber", blockNumber) + case req := <-b.replayChannel: + b.onReplayRequest(req) return true, nil case <-debounceResubscribe.C: @@ -444,6 +457,40 @@ func (b *broadcaster) eventLoop(chRawLogs <-chan types.Log, chErr <-chan error) } } +// onReplayRequest clears the pool and sets the block backfill number. +func (b *broadcaster) onReplayRequest(replayReq replayRequest) { + _ = b.invalidatePool() + // NOTE: This ignores r.highestNumConfirmations, but it is + // generally assumed that this will only be performed rarely and + // manually by someone who knows what he is doing + b.backfillBlockNumber.SetValid(replayReq.fromBlock) + if replayReq.forceBroadcast { + ctx, cancel := utils.ContextFromChan(b.chStop) + defer cancel() + err := b.orm.MarkBroadcastsUnconsumed(replayReq.fromBlock, pg.WithParentCtx(ctx)) + if err != nil { + b.logger.Errorw("Error marking broadcasts as unconsumed", + "error", err, "fromBlock", replayReq.fromBlock) + } + } + b.logger.Debugw( + "Returning from the event loop to replay logs from specific block number", + "fromBlock", replayReq.fromBlock, + "forceBroadcast", replayReq.forceBroadcast, + ) +} + +func (b *broadcaster) invalidatePool() int64 { + if min := b.logPool.heap.FindMin(); min != nil { + b.logPool = newLogPool() + // Note: even if we crash right now, PendingMinBlock is preserved in the database and we will backfill the same. + blockNum := int64(min.(Uint64)) + b.backfillBlockNumber.SetValid(blockNum) + return blockNum + } + return -1 +} + func (b *broadcaster) onNewLog(log types.Log) { b.maybeWarnOnLargeBlockNumberDifference(int64(log.BlockNumber)) @@ -660,7 +707,8 @@ func (n *NullBroadcaster) Register(listener Listener, opts ListenerOpts) (unsubs return func() {} } -func (n *NullBroadcaster) ReplayFromBlock(number int64) {} +// ReplayFromBlock implements the Broadcaster interface. +func (n *NullBroadcaster) ReplayFromBlock(number int64, forceBroadcast bool) {} func (n *NullBroadcaster) BackfillBlockNumber() null.Int64 { return null.NewInt64(0, false) @@ -681,8 +729,12 @@ func (n *NullBroadcaster) AwaitDependents() <-chan struct{} { close(ch) return ch } -func (n *NullBroadcaster) DependentReady() {} -func (n *NullBroadcaster) Start() error { return nil } + +// DependentReady does noop for NullBroadcaster. +func (n *NullBroadcaster) DependentReady() {} + +// Start does noop for NullBroadcaster. +func (n *NullBroadcaster) Start(context.Context) error { return nil } func (n *NullBroadcaster) Close() error { return nil } func (n *NullBroadcaster) Healthy() error { return nil } func (n *NullBroadcaster) Ready() error { return nil } diff --git a/core/chains/evm/log/eth_subscriber.go b/core/chains/evm/log/eth_subscriber.go index 443ed990718..fd813742550 100644 --- a/core/chains/evm/log/eth_subscriber.go +++ b/core/chains/evm/log/eth_subscriber.go @@ -128,9 +128,8 @@ func (sub *ethSubscriber) backfillLogs(fromBlockOverride null.Int64, addresses [ } return true } - if len(batchLogs) > 0 { - sub.logger.Infow(fmt.Sprintf("LogBroadcaster: Fetched a batch of %v logs from %v to %v%s", len(batchLogs), from, to, elapsedMessage), "len", len(batchLogs), "fromBlock", from, "toBlock", to, "remaining", int64(latestHeight)-to) - } + + sub.logger.Infow(fmt.Sprintf("LogBroadcaster: Fetched a batch of %v logs from %v to %v%s", len(batchLogs), from, to, elapsedMessage), "len", len(batchLogs), "fromBlock", from, "toBlock", to, "remaining", int64(latestHeight)-to) select { case <-sub.chStop: diff --git a/core/chains/evm/log/helpers_test.go b/core/chains/evm/log/helpers_test.go index 7423d172124..8b6645e2116 100644 --- a/core/chains/evm/log/helpers_test.go +++ b/core/chains/evm/log/helpers_test.go @@ -18,6 +18,8 @@ import ( "go.uber.org/atomic" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/sqlx" + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" "github.com/smartcontractkit/chainlink/core/chains/evm/log" @@ -35,7 +37,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" - "github.com/smartcontractkit/sqlx" ) type broadcasterHelper struct { @@ -83,9 +84,6 @@ func newBroadcasterHelperWithEthClient(t *testing.T, ethClient evmclient.Client, } func (c broadcasterHelperCfg) newWithEthClient(t *testing.T, ethClient evmclient.Client) *broadcasterHelper { - if testing.Short() { - t.Skip("skipping due to broadcasterHelper") - } if c.db == nil { c.db = pgtest.NewSqlxDB(t) } @@ -119,7 +117,7 @@ func (c broadcasterHelperCfg) newWithEthClient(t *testing.T, ethClient evmclient } func (helper *broadcasterHelper) start() { - err := helper.lb.Start() + err := helper.lb.Start(testutils.Context(helper.t)) require.NoError(helper.t, err) } diff --git a/core/chains/evm/log/integration_test.go b/core/chains/evm/log/integration_test.go index 913ee61cd7b..f9990c655f7 100644 --- a/core/chains/evm/log/integration_test.go +++ b/core/chains/evm/log/integration_test.go @@ -180,7 +180,7 @@ func TestBroadcaster_BackfillOnNodeStartAndOnReplay(t *testing.T) { require.Eventually(t, func() bool { return helper.mockEth.subscribeCallCount() == 1 }, cltest.WaitTimeout(t), time.Second) require.Eventually(t, func() bool { return backfillCount.Load() == 1 }, cltest.WaitTimeout(t), time.Second) - helper.lb.ReplayFromBlock(replayFrom) + helper.lb.ReplayFromBlock(replayFrom, false) require.Eventually(t, func() bool { return backfillCount.Load() >= 2 }, cltest.WaitTimeout(t), time.Second) }() @@ -189,6 +189,77 @@ func TestBroadcaster_BackfillOnNodeStartAndOnReplay(t *testing.T) { helper.mockEth.assertExpectations(t) } +func TestBroadcaster_ReplaysLogs(t *testing.T) { + const ( + blockHeight = 10 + ) + + blocks := cltest.NewBlocks(t, blockHeight+3) + contract, err := flux_aggregator_wrapper.NewFluxAggregator(testutils.NewAddress(), nil) + require.NoError(t, err) + sentLogs := []types.Log{ + blocks.LogOnBlockNum(3, contract.Address()), + blocks.LogOnBlockNum(7, contract.Address()), + } + + mockEth := newMockEthClient(t, make(chan chan<- types.Log, 4), blockHeight, mockEthClientExpectedCalls{ + FilterLogs: 4, + FilterLogsResult: sentLogs, + }) + helper := newBroadcasterHelperWithEthClient(t, mockEth.ethClient, cltest.Head(blockHeight)) + helper.mockEth = mockEth + + listener := helper.newLogListenerWithJob("listener") + helper.register(listener, contract, 2) + + func() { + helper.start() + defer helper.stop() + + // To start, no logs are sent + require.Eventually(t, func() bool { return len(listener.getUniqueLogs()) == 0 }, cltest.WaitTimeout(t), time.Second, + "expected unique logs to be 0 but was %d", len(listener.getUniqueLogs())) + + // Replay from block 2, the logs should be delivered. An incoming head must be simulated to + // trigger log delivery. + helper.lb.ReplayFromBlock(2, false) + <-cltest.SimulateIncomingHeads(t, cltest.SimulateIncomingHeadsArgs{ + StartBlock: 10, + EndBlock: 10, + HeadTrackables: []httypes.HeadTrackable{(helper.lb).(httypes.HeadTrackable)}, + Blocks: blocks, + }) + require.Eventually(t, func() bool { return len(listener.getUniqueLogs()) == 2 }, cltest.WaitTimeout(t), time.Second, + "expected unique logs to be 2 but was %d", len(listener.getUniqueLogs())) + + // Replay again, the logs are already marked consumed so they should not be included in + // getUniqueLogs. + helper.lb.ReplayFromBlock(2, false) + <-cltest.SimulateIncomingHeads(t, cltest.SimulateIncomingHeadsArgs{ + StartBlock: 11, + EndBlock: 11, + HeadTrackables: []httypes.HeadTrackable{(helper.lb).(httypes.HeadTrackable)}, + Blocks: blocks, + }) + require.Eventually(t, func() bool { return len(listener.getUniqueLogs()) == 2 }, cltest.WaitTimeout(t), time.Second, + "expected unique logs to be 2 but was %d", len(listener.getUniqueLogs())) + + // Replay again with forceBroadcast. The logs are consumed again. + helper.lb.ReplayFromBlock(2, true) + <-cltest.SimulateIncomingHeads(t, cltest.SimulateIncomingHeadsArgs{ + StartBlock: 12, + EndBlock: 12, + HeadTrackables: []httypes.HeadTrackable{(helper.lb).(httypes.HeadTrackable)}, + Blocks: blocks, + }) + require.Eventually(t, func() bool { return len(listener.getUniqueLogs()) == 4 }, cltest.WaitTimeout(t), time.Second, + "expected unique logs to be 4 but was %d", len(listener.getUniqueLogs())) + + }() + + require.Eventually(t, func() bool { return helper.mockEth.unsubscribeCallCount() >= 1 }, cltest.WaitTimeout(t), time.Second) +} + func TestBroadcaster_BackfillUnconsumedAfterCrash(t *testing.T) { db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) @@ -1172,7 +1243,7 @@ func TestBroadcaster_Register_ResubscribesToMostRecentlySeenBlock(t *testing.T) } // ReplayFrom will not lead to backfill because the number is above current height - helper.lb.ReplayFromBlock(125) + helper.lb.ReplayFromBlock(125, false) select { case <-chchRawLogs: diff --git a/core/chains/evm/log/mock_iLogPool_test.go b/core/chains/evm/log/mock_iLogPool_test.go index 291d041d1ac..2559fae11c1 100644 --- a/core/chains/evm/log/mock_iLogPool_test.go +++ b/core/chains/evm/log/mock_iLogPool_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery 2.9.4. DO NOT EDIT. +// Code generated by mockery v2.8.0. DO NOT EDIT. package log @@ -14,8 +14,8 @@ type mockILogPool struct { mock.Mock } -// AddLog provides a mock function with given fields: log -func (_m *mockILogPool) AddLog(log types.Log) bool { +// addLog provides a mock function with given fields: log +func (_m *mockILogPool) addLog(log types.Log) bool { ret := _m.Called(log) var r0 bool @@ -28,8 +28,8 @@ func (_m *mockILogPool) AddLog(log types.Log) bool { return r0 } -// DeleteOlderLogs provides a mock function with given fields: keptDepth -func (_m *mockILogPool) DeleteOlderLogs(keptDepth int64) *int64 { +// deleteOlderLogs provides a mock function with given fields: keptDepth +func (_m *mockILogPool) deleteOlderLogs(keptDepth int64) *int64 { ret := _m.Called(keptDepth) var r0 *int64 @@ -44,8 +44,8 @@ func (_m *mockILogPool) DeleteOlderLogs(keptDepth int64) *int64 { return r0 } -// GetAndDeleteAll provides a mock function with given fields: -func (_m *mockILogPool) GetAndDeleteAll() ([]logsOnBlock, int64, int64) { +// getAndDeleteAll provides a mock function with given fields: +func (_m *mockILogPool) getAndDeleteAll() ([]logsOnBlock, int64, int64) { ret := _m.Called() var r0 []logsOnBlock @@ -74,8 +74,8 @@ func (_m *mockILogPool) GetAndDeleteAll() ([]logsOnBlock, int64, int64) { return r0, r1, r2 } -// GetLogsToSend provides a mock function with given fields: latestBlockNum -func (_m *mockILogPool) GetLogsToSend(latestBlockNum int64) ([]logsOnBlock, int64) { +// getLogsToSend provides a mock function with given fields: latestBlockNum +func (_m *mockILogPool) getLogsToSend(latestBlockNum int64) ([]logsOnBlock, int64) { ret := _m.Called(latestBlockNum) var r0 []logsOnBlock @@ -97,13 +97,13 @@ func (_m *mockILogPool) GetLogsToSend(latestBlockNum int64) ([]logsOnBlock, int6 return r0, r1 } -// RemoveBlock provides a mock function with given fields: blockHash, blockNumber -func (_m *mockILogPool) RemoveBlock(blockHash common.Hash, blockNumber uint64) { - _m.Called(blockHash, blockNumber) +// removeBlock provides a mock function with given fields: hash, number +func (_m *mockILogPool) removeBlock(hash common.Hash, number uint64) { + _m.Called(hash, number) } -// TestOnly_getNumLogsForBlock provides a mock function with given fields: bh -func (_m *mockILogPool) TestOnly_getNumLogsForBlock(bh common.Hash) int { +// testOnly_getNumLogsForBlock provides a mock function with given fields: bh +func (_m *mockILogPool) testOnly_getNumLogsForBlock(bh common.Hash) int { ret := _m.Called(bh) var r0 int diff --git a/core/chains/evm/log/mocks/broadcaster.go b/core/chains/evm/log/mocks/broadcaster.go index 84abb49395d..f90e55dcacf 100644 --- a/core/chains/evm/log/mocks/broadcaster.go +++ b/core/chains/evm/log/mocks/broadcaster.go @@ -142,18 +142,18 @@ func (_m *Broadcaster) Register(listener log.Listener, opts log.ListenerOpts) fu return r0 } -// ReplayFromBlock provides a mock function with given fields: number -func (_m *Broadcaster) ReplayFromBlock(number int64) { - _m.Called(number) +// ReplayFromBlock provides a mock function with given fields: number, forceBroadcast +func (_m *Broadcaster) ReplayFromBlock(number int64, forceBroadcast bool) { + _m.Called(number, forceBroadcast) } -// Start provides a mock function with given fields: -func (_m *Broadcaster) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Broadcaster) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/log/mocks/orm.go b/core/chains/evm/log/mocks/orm.go index 902b48df6c2..e6f329773f7 100644 --- a/core/chains/evm/log/mocks/orm.go +++ b/core/chains/evm/log/mocks/orm.go @@ -109,6 +109,27 @@ func (_m *ORM) MarkBroadcastConsumed(blockHash common.Hash, blockNumber uint64, return r0 } +// MarkBroadcastsUnconsumed provides a mock function with given fields: fromBlock, qopts +func (_m *ORM) MarkBroadcastsUnconsumed(fromBlock int64, qopts ...pg.QOpt) error { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, fromBlock) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(int64, ...pg.QOpt) error); ok { + r0 = rf(fromBlock, qopts...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Reinitialize provides a mock function with given fields: qopts func (_m *ORM) Reinitialize(qopts ...pg.QOpt) (*int64, error) { _va := make([]interface{}, len(qopts)) diff --git a/core/chains/evm/log/orm.go b/core/chains/evm/log/orm.go index 504cbdc5f71..3761b54220a 100644 --- a/core/chains/evm/log/orm.go +++ b/core/chains/evm/log/orm.go @@ -31,6 +31,9 @@ type ORM interface { WasBroadcastConsumed(blockHash common.Hash, logIndex uint, jobID int32, qopts ...pg.QOpt) (bool, error) // MarkBroadcastConsumed marks the log broadcast as consumed by jobID. MarkBroadcastConsumed(blockHash common.Hash, blockNumber uint64, logIndex uint, jobID int32, qopts ...pg.QOpt) error + // MarkBroadcastsUnconsumed marks all log broadcasts from all jobs on or after fromBlock as + // unconsumed. + MarkBroadcastsUnconsumed(fromBlock int64, qopts ...pg.QOpt) error // SetPendingMinBlock sets the minimum block number for which there are pending broadcasts in the pool, or nil if empty. SetPendingMinBlock(blockNum *int64, qopts ...pg.QOpt) error @@ -110,6 +113,18 @@ func (o *orm) MarkBroadcastConsumed(blockHash common.Hash, blockNumber uint64, l return errors.Wrap(err, "failed to mark log broadcast as consumed") } +// MarkBroadcastsUnconsumed implements the ORM interface. +func (o *orm) MarkBroadcastsUnconsumed(fromBlock int64, qopts ...pg.QOpt) error { + q := o.q.WithOpts(qopts...) + err := q.ExecQ(` + UPDATE log_broadcasts + SET consumed = false + WHERE block_number >= $1 + AND evm_chain_id = $2 + `, fromBlock, o.evmChainID) + return errors.Wrap(err, "failed to mark broadcasts unconsumed") +} + func (o *orm) Reinitialize(qopts ...pg.QOpt) (*int64, error) { // Minimum block number from the set of unconsumed logs, which we'll remove later. minUnconsumed, err := o.getUnconsumedMinBlock(qopts...) diff --git a/core/chains/evm/log/orm_test.go b/core/chains/evm/log/orm_test.go index b357191dcf8..9d57d438ac2 100644 --- a/core/chains/evm/log/orm_test.go +++ b/core/chains/evm/log/orm_test.go @@ -106,6 +106,57 @@ func TestORM_pending(t *testing.T) { require.Nil(t, num) } +func TestORM_MarkUnconsumed(t *testing.T) { + db := pgtest.NewSqlxDB(t) + cfg := cltest.NewTestGeneralConfig(t) + lggr := logger.TestLogger(t) + ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() + + orm := log.NewORM(db, lggr, cfg, cltest.FixtureChainID) + + _, addr1 := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) + job1 := cltest.MustInsertV2JobSpec(t, db, addr1) + + _, addr2 := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) + job2 := cltest.MustInsertV2JobSpec(t, db, addr2) + + logBefore := cltest.RandomLog(t) + logBefore.BlockNumber = 34 + require.NoError(t, + orm.CreateBroadcast(logBefore.BlockHash, logBefore.BlockNumber, logBefore.Index, job1.ID)) + require.NoError(t, + orm.MarkBroadcastConsumed(logBefore.BlockHash, logBefore.BlockNumber, logBefore.Index, job1.ID)) + + logAt := cltest.RandomLog(t) + logAt.BlockNumber = 38 + require.NoError(t, + orm.CreateBroadcast(logAt.BlockHash, logAt.BlockNumber, logAt.Index, job1.ID)) + require.NoError(t, + orm.MarkBroadcastConsumed(logAt.BlockHash, logAt.BlockNumber, logAt.Index, job1.ID)) + + logAfter := cltest.RandomLog(t) + logAfter.BlockNumber = 40 + require.NoError(t, + orm.CreateBroadcast(logAfter.BlockHash, logAfter.BlockNumber, logAfter.Index, job2.ID)) + require.NoError(t, + orm.MarkBroadcastConsumed(logAfter.BlockHash, logAfter.BlockNumber, logAfter.Index, job2.ID)) + + // logAt and logAfter should now be marked unconsumed. logBefore is still consumed. + require.NoError(t, orm.MarkBroadcastsUnconsumed(38)) + + consumed, err := orm.WasBroadcastConsumed(logBefore.BlockHash, logBefore.Index, job1.ID) + require.NoError(t, err) + require.True(t, consumed) + + consumed, err = orm.WasBroadcastConsumed(logAt.BlockHash, logAt.Index, job1.ID) + require.NoError(t, err) + require.False(t, consumed) + + consumed, err = orm.WasBroadcastConsumed(logAfter.BlockHash, logAfter.Index, job2.ID) + require.NoError(t, err) + require.False(t, consumed) +} + func TestORM_Reinitialize(t *testing.T) { type TestLogBroadcast struct { BlockNumber big.Int diff --git a/core/chains/evm/log/registrations_test.go b/core/chains/evm/log/registrations_test.go index 1482a0e6c2e..3f18b0669db 100644 --- a/core/chains/evm/log/registrations_test.go +++ b/core/chains/evm/log/registrations_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/test-go/testify/assert" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" diff --git a/core/chains/evm/mocks/balance_monitor.go b/core/chains/evm/mocks/balance_monitor.go index 0403e851e03..afefda289b6 100644 --- a/core/chains/evm/mocks/balance_monitor.go +++ b/core/chains/evm/mocks/balance_monitor.go @@ -3,9 +3,8 @@ package mocks import ( - assets "github.com/smartcontractkit/chainlink/core/assets" - common "github.com/ethereum/go-ethereum/common" + assets "github.com/smartcontractkit/chainlink/core/assets" context "context" @@ -82,13 +81,13 @@ func (_m *BalanceMonitor) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *BalanceMonitor) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *BalanceMonitor) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/mocks/chain.go b/core/chains/evm/mocks/chain.go index fb188784d94..50909d9dd38 100644 --- a/core/chains/evm/mocks/chain.go +++ b/core/chains/evm/mocks/chain.go @@ -5,20 +5,21 @@ package mocks import ( big "math/big" - balancemonitor "github.com/smartcontractkit/chainlink/core/chains/evm/balancemonitor" - - bulletprooftxmanager "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - client "github.com/smartcontractkit/chainlink/core/chains/evm/client" - config "github.com/smartcontractkit/chainlink/core/chains/evm/config" + context "context" + log "github.com/smartcontractkit/chainlink/core/chains/evm/log" logger "github.com/smartcontractkit/chainlink/core/logger" mock "github.com/stretchr/testify/mock" + monitor "github.com/smartcontractkit/chainlink/core/chains/evm/monitor" + + txmgr "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + types "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" ) @@ -28,15 +29,15 @@ type Chain struct { } // BalanceMonitor provides a mock function with given fields: -func (_m *Chain) BalanceMonitor() balancemonitor.BalanceMonitor { +func (_m *Chain) BalanceMonitor() monitor.BalanceMonitor { ret := _m.Called() - var r0 balancemonitor.BalanceMonitor - if rf, ok := ret.Get(0).(func() balancemonitor.BalanceMonitor); ok { + var r0 monitor.BalanceMonitor + if rf, ok := ret.Get(0).(func() monitor.BalanceMonitor); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(balancemonitor.BalanceMonitor) + r0 = ret.Get(0).(monitor.BalanceMonitor) } } @@ -197,13 +198,13 @@ func (_m *Chain) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *Chain) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Chain) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } @@ -212,15 +213,15 @@ func (_m *Chain) Start() error { } // TxManager provides a mock function with given fields: -func (_m *Chain) TxManager() bulletprooftxmanager.TxManager { +func (_m *Chain) TxManager() txmgr.TxManager { ret := _m.Called() - var r0 bulletprooftxmanager.TxManager - if rf, ok := ret.Get(0).(func() bulletprooftxmanager.TxManager); ok { + var r0 txmgr.TxManager + if rf, ok := ret.Get(0).(func() txmgr.TxManager); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(bulletprooftxmanager.TxManager) + r0 = ret.Get(0).(txmgr.TxManager) } } diff --git a/core/chains/evm/mocks/chain_set.go b/core/chains/evm/mocks/chain_set.go index ce7e408c552..338d102441a 100644 --- a/core/chains/evm/mocks/chain_set.go +++ b/core/chains/evm/mocks/chain_set.go @@ -3,12 +3,15 @@ package mocks import ( + context "context" big "math/big" evm "github.com/smartcontractkit/chainlink/core/chains/evm" mock "github.com/stretchr/testify/mock" types "github.com/smartcontractkit/chainlink/core/chains/evm/types" + + utils "github.com/smartcontractkit/chainlink/core/utils" ) // ChainSet is an autogenerated mock type for the ChainSet type @@ -16,20 +19,20 @@ type ChainSet struct { mock.Mock } -// Add provides a mock function with given fields: id, config -func (_m *ChainSet) Add(id *big.Int, config types.ChainCfg) (types.Chain, error) { - ret := _m.Called(id, config) +// Add provides a mock function with given fields: ctx, id, config +func (_m *ChainSet) Add(ctx context.Context, id *big.Int, config types.ChainCfg) (types.Chain, error) { + ret := _m.Called(ctx, id, config) var r0 types.Chain - if rf, ok := ret.Get(0).(func(*big.Int, types.ChainCfg) types.Chain); ok { - r0 = rf(id, config) + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, types.ChainCfg) types.Chain); ok { + r0 = rf(ctx, id, config) } else { r0 = ret.Get(0).(types.Chain) } var r1 error - if rf, ok := ret.Get(1).(func(*big.Int, types.ChainCfg) error); ok { - r1 = rf(id, config) + if rf, ok := ret.Get(1).(func(context.Context, *big.Int, types.ChainCfg) error); ok { + r1 = rf(ctx, id, config) } else { r1 = ret.Error(1) } @@ -81,20 +84,20 @@ func (_m *ChainSet) Close() error { return r0 } -// Configure provides a mock function with given fields: id, enabled, config -func (_m *ChainSet) Configure(id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) { - ret := _m.Called(id, enabled, config) +// Configure provides a mock function with given fields: ctx, id, enabled, config +func (_m *ChainSet) Configure(ctx context.Context, id *big.Int, enabled bool, config types.ChainCfg) (types.Chain, error) { + ret := _m.Called(ctx, id, enabled, config) var r0 types.Chain - if rf, ok := ret.Get(0).(func(*big.Int, bool, types.ChainCfg) types.Chain); ok { - r0 = rf(id, enabled, config) + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, bool, types.ChainCfg) types.Chain); ok { + r0 = rf(ctx, id, enabled, config) } else { r0 = ret.Get(0).(types.Chain) } var r1 error - if rf, ok := ret.Get(1).(func(*big.Int, bool, types.ChainCfg) error); ok { - r1 = rf(id, enabled, config) + if rf, ok := ret.Get(1).(func(context.Context, *big.Int, bool, types.ChainCfg) error); ok { + r1 = rf(ctx, id, enabled, config) } else { r1 = ret.Error(1) } @@ -148,6 +151,110 @@ func (_m *ChainSet) Get(id *big.Int) (evm.Chain, error) { return r0, r1 } +// GetNode provides a mock function with given fields: ctx, id +func (_m *ChainSet) GetNode(ctx context.Context, id int32) (types.Node, error) { + ret := _m.Called(ctx, id) + + var r0 types.Node + if rf, ok := ret.Get(0).(func(context.Context, int32) types.Node); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(types.Node) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int32) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNodes provides a mock function with given fields: ctx, offset, limit +func (_m *ChainSet) GetNodes(ctx context.Context, offset int, limit int) ([]types.Node, int, error) { + ret := _m.Called(ctx, offset, limit) + + var r0 []types.Node + if rf, ok := ret.Get(0).(func(context.Context, int, int) []types.Node); ok { + r0 = rf(ctx, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Node) + } + } + + var r1 int + if rf, ok := ret.Get(1).(func(context.Context, int, int) int); ok { + r1 = rf(ctx, offset, limit) + } else { + r1 = ret.Get(1).(int) + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, int, int) error); ok { + r2 = rf(ctx, offset, limit) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetNodesByChainIDs provides a mock function with given fields: ctx, chainIDs +func (_m *ChainSet) GetNodesByChainIDs(ctx context.Context, chainIDs []utils.Big) ([]types.Node, error) { + ret := _m.Called(ctx, chainIDs) + + var r0 []types.Node + if rf, ok := ret.Get(0).(func(context.Context, []utils.Big) []types.Node); ok { + r0 = rf(ctx, chainIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Node) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []utils.Big) error); ok { + r1 = rf(ctx, chainIDs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNodesForChain provides a mock function with given fields: ctx, chainID, offset, limit +func (_m *ChainSet) GetNodesForChain(ctx context.Context, chainID utils.Big, offset int, limit int) ([]types.Node, int, error) { + ret := _m.Called(ctx, chainID, offset, limit) + + var r0 []types.Node + if rf, ok := ret.Get(0).(func(context.Context, utils.Big, int, int) []types.Node); ok { + r0 = rf(ctx, chainID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Node) + } + } + + var r1 int + if rf, ok := ret.Get(1).(func(context.Context, utils.Big, int, int) int); ok { + r1 = rf(ctx, chainID, offset, limit) + } else { + r1 = ret.Get(1).(int) + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, utils.Big, int, int) error); ok { + r2 = rf(ctx, chainID, offset, limit) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // Healthy provides a mock function with given fields: func (_m *ChainSet) Healthy() error { ret := _m.Called() @@ -206,13 +313,13 @@ func (_m *ChainSet) Remove(id *big.Int) error { return r0 } -// Start provides a mock function with given fields: -func (_m *ChainSet) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *ChainSet) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/mocks/client.go b/core/chains/evm/mocks/client.go index 892a43c5b37..4732e63d26d 100644 --- a/core/chains/evm/mocks/client.go +++ b/core/chains/evm/mocks/client.go @@ -64,6 +64,20 @@ func (_m *Client) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error return r0 } +// BatchCallContextAll provides a mock function with given fields: ctx, b +func (_m *Client) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + ret := _m.Called(ctx, b) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []rpc.BatchElem) error); ok { + r0 = rf(ctx, b) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // BlockByNumber provides a mock function with given fields: ctx, number func (_m *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { ret := _m.Called(ctx, number) @@ -361,6 +375,22 @@ func (_m *Client) HeaderByNumber(_a0 context.Context, _a1 *big.Int) (*types.Head return r0, r1 } +// NodeStates provides a mock function with given fields: +func (_m *Client) NodeStates() map[int32]string { + ret := _m.Called() + + var r0 map[int32]string + if rf, ok := ret.Get(0).(func() map[int32]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[int32]string) + } + } + + return r0 +} + // NonceAt provides a mock function with given fields: ctx, account, blockNumber func (_m *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { ret := _m.Called(ctx, account, blockNumber) diff --git a/core/chains/evm/mocks/node.go b/core/chains/evm/mocks/node.go index b5d6d258401..5fe845eef54 100644 --- a/core/chains/evm/mocks/node.go +++ b/core/chains/evm/mocks/node.go @@ -12,6 +12,8 @@ import ( ethereum "github.com/ethereum/go-ethereum" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + mock "github.com/stretchr/testify/mock" rpc "github.com/ethereum/go-ethereum/rpc" @@ -124,27 +126,20 @@ func (_m *Node) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNum return r0, r1 } -// ChainID provides a mock function with given fields: ctx -func (_m *Node) ChainID(ctx context.Context) (*big.Int, error) { - ret := _m.Called(ctx) +// ChainID provides a mock function with given fields: +func (_m *Node) ChainID() *big.Int { + ret := _m.Called() var r0 *big.Int - if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { - r0 = rf(ctx) + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*big.Int) } } - var r1 error - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 + return r0 } // Close provides a mock function with given fields: @@ -175,20 +170,6 @@ func (_m *Node) CodeAt(ctx context.Context, account common.Address, blockNumber return r0, r1 } -// Dial provides a mock function with given fields: ctx -func (_m *Node) Dial(ctx context.Context) error { - ret := _m.Called(ctx) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // EstimateGas provides a mock function with given fields: ctx, call func (_m *Node) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { ret := _m.Called(ctx, call) @@ -211,14 +192,14 @@ func (_m *Node) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, } // EthSubscribe provides a mock function with given fields: ctx, channel, args -func (_m *Node) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error) { +func (_m *Node) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (ethereum.Subscription, error) { var _ca []interface{} _ca = append(_ca, ctx, channel) _ca = append(_ca, args...) ret := _m.Called(_ca...) var r0 ethereum.Subscription - if rf, ok := ret.Get(0).(func(context.Context, interface{}, ...interface{}) ethereum.Subscription); ok { + if rf, ok := ret.Get(0).(func(context.Context, chan<- *evmtypes.Head, ...interface{}) ethereum.Subscription); ok { r0 = rf(ctx, channel, args...) } else { if ret.Get(0) != nil { @@ -227,7 +208,7 @@ func (_m *Node) EthSubscribe(ctx context.Context, channel interface{}, args ...i } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, interface{}, ...interface{}) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, chan<- *evmtypes.Head, ...interface{}) error); ok { r1 = rf(ctx, channel, args...) } else { r1 = ret.Error(1) @@ -282,6 +263,20 @@ func (_m *Node) HeaderByNumber(_a0 context.Context, _a1 *big.Int) (*types.Header return r0, r1 } +// ID provides a mock function with given fields: +func (_m *Node) ID() int32 { + ret := _m.Called() + + var r0 int32 + if rf, ok := ret.Get(0).(func() int32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int32) + } + + return r0 +} + // NonceAt provides a mock function with given fields: ctx, account, blockNumber func (_m *Node) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { ret := _m.Called(ctx, account, blockNumber) @@ -361,6 +356,20 @@ func (_m *Node) SendTransaction(ctx context.Context, tx *types.Transaction) erro return r0 } +// Start provides a mock function with given fields: ctx +func (_m *Node) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // State provides a mock function with given fields: func (_m *Node) State() client.NodeState { ret := _m.Called() @@ -480,17 +489,3 @@ func (_m *Node) TransactionReceipt(ctx context.Context, txHash common.Hash) (*ty return r0, r1 } - -// Verify provides a mock function with given fields: ctx, expectedChainID -func (_m *Node) Verify(ctx context.Context, expectedChainID *big.Int) error { - ret := _m.Called(ctx, expectedChainID) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *big.Int) error); ok { - r0 = rf(ctx, expectedChainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/core/chains/evm/mocks/orm.go b/core/chains/evm/mocks/orm.go index 05dd59290b6..f95ae9c2630 100644 --- a/core/chains/evm/mocks/orm.go +++ b/core/chains/evm/mocks/orm.go @@ -5,9 +5,11 @@ package mocks import ( big "math/big" - types "github.com/smartcontractkit/chainlink/core/chains/evm/types" + pg "github.com/smartcontractkit/chainlink/core/services/pg" mock "github.com/stretchr/testify/mock" + types "github.com/smartcontractkit/chainlink/core/chains/evm/types" + utils "github.com/smartcontractkit/chainlink/core/utils" ) @@ -197,13 +199,20 @@ func (_m *ORM) GetChainsByIDs(ids []utils.Big) ([]types.Chain, error) { return r0, r1 } -// GetNodesByChainIDs provides a mock function with given fields: chainIDs -func (_m *ORM) GetNodesByChainIDs(chainIDs []utils.Big) ([]types.Node, error) { - ret := _m.Called(chainIDs) +// GetNodesByChainIDs provides a mock function with given fields: chainIDs, qopts +func (_m *ORM) GetNodesByChainIDs(chainIDs []utils.Big, qopts ...pg.QOpt) ([]types.Node, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, chainIDs) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 []types.Node - if rf, ok := ret.Get(0).(func([]utils.Big) []types.Node); ok { - r0 = rf(chainIDs) + if rf, ok := ret.Get(0).(func([]utils.Big, ...pg.QOpt) []types.Node); ok { + r0 = rf(chainIDs, qopts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]types.Node) @@ -211,8 +220,8 @@ func (_m *ORM) GetNodesByChainIDs(chainIDs []utils.Big) ([]types.Node, error) { } var r1 error - if rf, ok := ret.Get(1).(func([]utils.Big) error); ok { - r1 = rf(chainIDs) + if rf, ok := ret.Get(1).(func([]utils.Big, ...pg.QOpt) error); ok { + r1 = rf(chainIDs, qopts...) } else { r1 = ret.Error(1) } @@ -220,20 +229,27 @@ func (_m *ORM) GetNodesByChainIDs(chainIDs []utils.Big) ([]types.Node, error) { return r0, r1 } -// Node provides a mock function with given fields: id -func (_m *ORM) Node(id int32) (types.Node, error) { - ret := _m.Called(id) +// Node provides a mock function with given fields: id, qopts +func (_m *ORM) Node(id int32, qopts ...pg.QOpt) (types.Node, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, id) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 types.Node - if rf, ok := ret.Get(0).(func(int32) types.Node); ok { - r0 = rf(id) + if rf, ok := ret.Get(0).(func(int32, ...pg.QOpt) types.Node); ok { + r0 = rf(id, qopts...) } else { r0 = ret.Get(0).(types.Node) } var r1 error - if rf, ok := ret.Get(1).(func(int32) error); ok { - r1 = rf(id) + if rf, ok := ret.Get(1).(func(int32, ...pg.QOpt) error); ok { + r1 = rf(id, qopts...) } else { r1 = ret.Error(1) } @@ -241,13 +257,20 @@ func (_m *ORM) Node(id int32) (types.Node, error) { return r0, r1 } -// Nodes provides a mock function with given fields: offset, limit -func (_m *ORM) Nodes(offset int, limit int) ([]types.Node, int, error) { - ret := _m.Called(offset, limit) +// Nodes provides a mock function with given fields: offset, limit, qopts +func (_m *ORM) Nodes(offset int, limit int, qopts ...pg.QOpt) ([]types.Node, int, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, offset, limit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 []types.Node - if rf, ok := ret.Get(0).(func(int, int) []types.Node); ok { - r0 = rf(offset, limit) + if rf, ok := ret.Get(0).(func(int, int, ...pg.QOpt) []types.Node); ok { + r0 = rf(offset, limit, qopts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]types.Node) @@ -255,15 +278,15 @@ func (_m *ORM) Nodes(offset int, limit int) ([]types.Node, int, error) { } var r1 int - if rf, ok := ret.Get(1).(func(int, int) int); ok { - r1 = rf(offset, limit) + if rf, ok := ret.Get(1).(func(int, int, ...pg.QOpt) int); ok { + r1 = rf(offset, limit, qopts...) } else { r1 = ret.Get(1).(int) } var r2 error - if rf, ok := ret.Get(2).(func(int, int) error); ok { - r2 = rf(offset, limit) + if rf, ok := ret.Get(2).(func(int, int, ...pg.QOpt) error); ok { + r2 = rf(offset, limit, qopts...) } else { r2 = ret.Error(2) } @@ -271,13 +294,20 @@ func (_m *ORM) Nodes(offset int, limit int) ([]types.Node, int, error) { return r0, r1, r2 } -// NodesForChain provides a mock function with given fields: chainID, offset, limit -func (_m *ORM) NodesForChain(chainID utils.Big, offset int, limit int) ([]types.Node, int, error) { - ret := _m.Called(chainID, offset, limit) +// NodesForChain provides a mock function with given fields: chainID, offset, limit, qopts +func (_m *ORM) NodesForChain(chainID utils.Big, offset int, limit int, qopts ...pg.QOpt) ([]types.Node, int, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, chainID, offset, limit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 []types.Node - if rf, ok := ret.Get(0).(func(utils.Big, int, int) []types.Node); ok { - r0 = rf(chainID, offset, limit) + if rf, ok := ret.Get(0).(func(utils.Big, int, int, ...pg.QOpt) []types.Node); ok { + r0 = rf(chainID, offset, limit, qopts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]types.Node) @@ -285,15 +315,15 @@ func (_m *ORM) NodesForChain(chainID utils.Big, offset int, limit int) ([]types. } var r1 int - if rf, ok := ret.Get(1).(func(utils.Big, int, int) int); ok { - r1 = rf(chainID, offset, limit) + if rf, ok := ret.Get(1).(func(utils.Big, int, int, ...pg.QOpt) int); ok { + r1 = rf(chainID, offset, limit, qopts...) } else { r1 = ret.Get(1).(int) } var r2 error - if rf, ok := ret.Get(2).(func(utils.Big, int, int) error); ok { - r2 = rf(chainID, offset, limit) + if rf, ok := ret.Get(2).(func(utils.Big, int, int, ...pg.QOpt) error); ok { + r2 = rf(chainID, offset, limit, qopts...) } else { r2 = ret.Error(2) } diff --git a/core/chains/evm/mocks/send_only_node.go b/core/chains/evm/mocks/send_only_node.go index c98225b72ca..3cc551e6dfb 100644 --- a/core/chains/evm/mocks/send_only_node.go +++ b/core/chains/evm/mocks/send_only_node.go @@ -33,36 +33,34 @@ func (_m *SendOnlyNode) BatchCallContext(ctx context.Context, b []rpc.BatchElem) return r0 } -// ChainID provides a mock function with given fields: ctx -func (_m *SendOnlyNode) ChainID(ctx context.Context) (*big.Int, error) { - ret := _m.Called(ctx) +// ChainID provides a mock function with given fields: +func (_m *SendOnlyNode) ChainID() *big.Int { + ret := _m.Called() var r0 *big.Int - if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { - r0 = rf(ctx) + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*big.Int) } } - var r1 error - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } + return r0 +} - return r0, r1 +// Close provides a mock function with given fields: +func (_m *SendOnlyNode) Close() { + _m.Called() } -// Dial provides a mock function with given fields: _a0 -func (_m *SendOnlyNode) Dial(_a0 context.Context) error { - ret := _m.Called(_a0) +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *SendOnlyNode) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { + r0 = rf(ctx, tx) } else { r0 = ret.Error(0) } @@ -70,13 +68,13 @@ func (_m *SendOnlyNode) Dial(_a0 context.Context) error { return r0 } -// SendTransaction provides a mock function with given fields: ctx, tx -func (_m *SendOnlyNode) SendTransaction(ctx context.Context, tx *types.Transaction) error { - ret := _m.Called(ctx, tx) +// Start provides a mock function with given fields: _a0 +func (_m *SendOnlyNode) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { - r0 = rf(ctx, tx) + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } @@ -97,17 +95,3 @@ func (_m *SendOnlyNode) String() string { return r0 } - -// Verify provides a mock function with given fields: ctx, expectedChainID -func (_m *SendOnlyNode) Verify(ctx context.Context, expectedChainID *big.Int) error { - ret := _m.Called(ctx, expectedChainID) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *big.Int) error); ok { - r0 = rf(ctx, expectedChainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/core/chains/evm/balancemonitor/balance_monitor.go b/core/chains/evm/monitor/balance.go similarity index 92% rename from core/chains/evm/balancemonitor/balance_monitor.go rename to core/chains/evm/monitor/balance.go index dc39e8ea58c..c504f5a445b 100644 --- a/core/chains/evm/balancemonitor/balance_monitor.go +++ b/core/chains/evm/monitor/balance.go @@ -1,4 +1,4 @@ -package balancemonitor +package monitor import ( "context" @@ -30,7 +30,7 @@ type ( BalanceMonitor interface { httypes.HeadTrackable GetEthBalance(gethCommon.Address) *assets.Eth - services.Service + services.ServiceCtx } balanceMonitor struct { @@ -63,10 +63,10 @@ func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, log return bm } -func (bm *balanceMonitor) Start() error { +func (bm *balanceMonitor) Start(ctx context.Context) error { return bm.StartOnce("BalanceMonitor", func() error { // Always query latest balance on start - (&worker{bm}).Work() + (&worker{bm}).WorkCtx(ctx) return nil }) } @@ -159,6 +159,11 @@ func (*worker) Name() string { } func (w *worker) Work() { + // Used with SleeperTask + w.WorkCtx(context.Background()) +} + +func (w *worker) WorkCtx(ctx context.Context) { keys, err := w.bm.ethKeyStore.SendingKeys() if err != nil { w.bm.logger.Error("BalanceMonitor: error getting keys", err) @@ -170,7 +175,7 @@ func (w *worker) Work() { for _, key := range keys { go func(k ethkey.KeyV2) { defer wg.Done() - w.checkAccountBalance(k) + w.checkAccountBalance(ctx, k) }(key) } wg.Wait() @@ -179,8 +184,8 @@ func (w *worker) Work() { // Approximately ETH block time const ethFetchTimeout = 15 * time.Second -func (w *worker) checkAccountBalance(k ethkey.KeyV2) { - ctx, cancel := context.WithTimeout(context.Background(), ethFetchTimeout) +func (w *worker) checkAccountBalance(ctx context.Context, k ethkey.KeyV2) { + ctx, cancel := context.WithTimeout(ctx, ethFetchTimeout) defer cancel() bal, err := w.bm.ethClient.BalanceAt(ctx, k.Address.Address(), nil) @@ -203,7 +208,9 @@ func (w *worker) checkAccountBalance(k ethkey.KeyV2) { func (*NullBalanceMonitor) GetEthBalance(gethCommon.Address) *assets.Eth { return nil } -func (*NullBalanceMonitor) Start() error { return nil } + +// Start does noop for NullBalanceMonitor. +func (*NullBalanceMonitor) Start(context.Context) error { return nil } func (*NullBalanceMonitor) Close() error { return nil } func (*NullBalanceMonitor) Ready() error { return nil } func (*NullBalanceMonitor) Healthy() error { return nil } diff --git a/core/chains/evm/balancemonitor/balance_monitor_test.go b/core/chains/evm/monitor/balance_test.go similarity index 78% rename from core/chains/evm/balancemonitor/balance_monitor_test.go rename to core/chains/evm/monitor/balance_test.go index b9ec36b0b5e..ed5eecf2e8f 100644 --- a/core/chains/evm/balancemonitor/balance_monitor_test.go +++ b/core/chains/evm/monitor/balance_test.go @@ -1,4 +1,4 @@ -package balancemonitor_test +package monitor_test import ( "context" @@ -6,8 +6,6 @@ import ( "testing" "time" - evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" - "github.com/onsi/gomega" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -16,8 +14,10 @@ import ( "go.uber.org/atomic" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/balancemonitor" + evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/monitor" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" ) @@ -44,7 +44,7 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) _, k1Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) - bm := balancemonitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) defer bm.Close() k0bal := big.NewInt(42) @@ -55,7 +55,7 @@ func TestBalanceMonitor_Start(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt).Once().Return(k0bal, nil) ethClient.On("BalanceAt", mock.Anything, k1Addr, nilBigInt).Once().Return(k1bal, nil) - assert.NoError(t, bm.Start()) + assert.NoError(t, bm.Start(testutils.Context(t))) gomega.NewWithT(t).Eventually(func() *big.Int { return bm.GetEthBalance(k0Addr).ToInt() @@ -74,19 +74,51 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) - bm := balancemonitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) defer bm.Close() k0bal := big.NewInt(42) ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt).Once().Return(k0bal, nil) - assert.NoError(t, bm.Start()) + assert.NoError(t, bm.Start(testutils.Context(t))) gomega.NewWithT(t).Eventually(func() *big.Int { return bm.GetEthBalance(k0Addr).ToInt() }).Should(gomega.Equal(k0bal)) }) + t.Run("cancelled context", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() + + ethClient := newEthClientMock(t) + defer ethClient.AssertExpectations(t) + + _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) + + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + defer bm.Close() + ctxCancelledAwaiter := cltest.NewAwaiter() + + ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt).Once().Run(func(args mock.Arguments) { + ctx := args.Get(0).(context.Context) + select { + case <-time.After(testutils.WaitTimeout(t)): + case <-ctx.Done(): + ctxCancelledAwaiter.ItHappened() + } + }).Return(nil, nil) + + ctx, cancel := context.WithCancel(testutils.Context(t)) + go func() { + <-time.After(time.Second) + cancel() + }() + assert.NoError(t, bm.Start(ctx)) + + ctxCancelledAwaiter.AwaitOrFail(t) + }) + t.Run("recovers on error", func(t *testing.T) { db := pgtest.NewSqlxDB(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -96,14 +128,14 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) - bm := balancemonitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) defer bm.Close() ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt). Once(). Return(nil, errors.New("a little easter egg for the 4chan link marines error")) - assert.NoError(t, bm.Start()) + assert.NoError(t, bm.Start(testutils.Context(t))) gomega.NewWithT(t).Consistently(func() *big.Int { return bm.GetEthBalance(k0Addr).ToInt() @@ -124,7 +156,7 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) _, k1Addr := cltest.MustInsertRandomKey(t, ethKeyStore, 0) - bm := balancemonitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) k0bal := big.NewInt(42) // Deliberately larger than a 64 bit unsigned integer to test overflow k1bal := big.NewInt(0) @@ -135,7 +167,7 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt).Once().Return(k0bal, nil) ethClient.On("BalanceAt", mock.Anything, k1Addr, nilBigInt).Once().Return(k1bal, nil) - require.NoError(t, bm.Start()) + require.NoError(t, bm.Start(testutils.Context(t))) defer bm.Close() ethClient.AssertExpectations(t) @@ -144,7 +176,7 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, k1Addr, nilBigInt).Once().Return(k1bal, nil) // Do the thing - bm.OnNewLongestChain(context.TODO(), head) + bm.OnNewLongestChain(testutils.Context(t), head) gomega.NewWithT(t).Eventually(func() *big.Int { return bm.GetEthBalance(k0Addr).ToInt() @@ -162,7 +194,7 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt).Once().Return(k0bal2, nil) ethClient.On("BalanceAt", mock.Anything, k1Addr, nilBigInt).Once().Return(k1bal2, nil) - bm.OnNewLongestChain(context.TODO(), head) + bm.OnNewLongestChain(testutils.Context(t), head) gomega.NewWithT(t).Eventually(func() *big.Int { return bm.GetEthBalance(k0Addr).ToInt() @@ -184,11 +216,11 @@ func TestBalanceMonitor_FewerRPCCallsWhenBehind(t *testing.T) { ethClient := newEthClientMock(t) - bm := balancemonitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything). Once(). Return(big.NewInt(1), nil) - require.NoError(t, bm.Start()) + require.NoError(t, bm.Start(testutils.Context(t))) head := cltest.Head(0) @@ -209,7 +241,7 @@ func TestBalanceMonitor_FewerRPCCallsWhenBehind(t *testing.T) { // Do the thing multiple times for i := 0; i < 10; i++ { - bm.OnNewLongestChain(context.TODO(), head) + bm.OnNewLongestChain(testutils.Context(t), head) } // Unblock the first mock @@ -243,7 +275,7 @@ func Test_ApproximateFloat64(t *testing.T) { t.Run(test.name, func(t *testing.T) { eth := assets.NewEth(0) eth.SetString(test.input, 10) - float, err := balancemonitor.ApproximateFloat64(eth) + float, err := monitor.ApproximateFloat64(eth) require.NoError(t, err) require.Equal(t, test.want, float) }) diff --git a/core/chains/evm/orm.go b/core/chains/evm/orm.go index ab8353243c3..b795e084961 100644 --- a/core/chains/evm/orm.go +++ b/core/chains/evm/orm.go @@ -7,19 +7,25 @@ import ( "github.com/lib/pq" "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) type orm struct { db *sqlx.DB + q pg.Q } var _ types.ORM = (*orm)(nil) -func NewORM(db *sqlx.DB) types.ORM { - return &orm{db} +// NewORM returns a new EVM ORM +func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig) types.ORM { + lggr = lggr.Named("EVMORM") + return &orm{db, pg.NewQ(db, lggr, cfg)} } func (o *orm) Chain(id utils.Big) (chain types.Chain, err error) { @@ -129,46 +135,49 @@ func (o *orm) EnabledChainsWithNodes() (chains []types.Chain, err error) { return chains, nil } -func (o *orm) Nodes(offset, limit int) (nodes []types.Node, count int, err error) { - if err = o.db.Get(&count, "SELECT COUNT(*) FROM evm_nodes"); err != nil { - return - } +func (o *orm) Nodes(offset, limit int, qopts ...pg.QOpt) (nodes []types.Node, count int, err error) { + err = o.q.WithOpts(qopts...).Transaction(func(q pg.Queryer) error { + if err = o.db.Get(&count, "SELECT COUNT(*) FROM evm_nodes"); err != nil { + return errors.Wrap(err, "Nodes failed to fetch nodes count") + } - sql := `SELECT * FROM evm_nodes ORDER BY created_at, id LIMIT $1 OFFSET $2;` - if err = o.db.Select(&nodes, sql, limit, offset); err != nil { - return - } + sql := `SELECT * FROM evm_nodes ORDER BY created_at, id LIMIT $1 OFFSET $2;` + err = o.db.Select(&nodes, sql, limit, offset) + return errors.Wrap(err, "Nodes failed to fetch nodes") + }) return } // GetNodesByChainIDs fetches allow nodes for the given chain ids. -func (o *orm) GetNodesByChainIDs(chainIDs []utils.Big) (nodes []types.Node, err error) { +func (o *orm) GetNodesByChainIDs(chainIDs []utils.Big, qopts ...pg.QOpt) (nodes []types.Node, err error) { sql := `SELECT * FROM evm_nodes WHERE evm_chain_id = ANY($1) ORDER BY created_at, id;` cids := pq.Array(chainIDs) - if err = o.db.Select(&nodes, sql, cids); err != nil { + if err = o.q.WithOpts(qopts...).Select(&nodes, sql, cids); err != nil { return nil, err } return nodes, nil } -func (o *orm) NodesForChain(chainID utils.Big, offset, limit int) (nodes []types.Node, count int, err error) { - if err = o.db.Get(&count, "SELECT COUNT(*) FROM evm_nodes WHERE evm_chain_id = $1", chainID); err != nil { - return - } +func (o *orm) NodesForChain(chainID utils.Big, offset, limit int, qopts ...pg.QOpt) (nodes []types.Node, count int, err error) { + err = o.q.WithOpts(qopts...).Transaction(func(q pg.Queryer) error { + if err = q.Get(&count, "SELECT COUNT(*) FROM evm_nodes WHERE evm_chain_id = $1", chainID); err != nil { + return errors.Wrap(err, "NodesForChain failed to fetch nodes count") + } - sql := `SELECT * FROM evm_nodes WHERE evm_chain_id = $1 ORDER BY created_at, id LIMIT $2 OFFSET $3;` - if err = o.db.Select(&nodes, sql, chainID, limit, offset); err != nil { - return - } + sql := `SELECT * FROM evm_nodes WHERE evm_chain_id = $1 ORDER BY created_at, id LIMIT $2 OFFSET $3;` + err = q.Select(&nodes, sql, chainID, limit, offset) + return errors.Wrap(err, "NodesForChain failed to fetch nodes") + }, pg.OptReadOnlyTx()) return } -func (o *orm) Node(id int32) (node types.Node, err error) { - err = o.db.Get(&node, "SELECT * FROM evm_nodes WHERE id = $1;", id) +func (o *orm) Node(id int32, qopts ...pg.QOpt) (node types.Node, err error) { + q := o.q.WithOpts(qopts...) + err = q.Get(&node, "SELECT * FROM evm_nodes WHERE id = $1;", id) return } diff --git a/core/chains/evm/orm_test.go b/core/chains/evm/orm_test.go index 2f49727c32e..992196b6b74 100644 --- a/core/chains/evm/orm_test.go +++ b/core/chains/evm/orm_test.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink/core/chains/evm" "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -19,7 +20,7 @@ func setupORM(t *testing.T) (*sqlx.DB, types.ORM) { t.Helper() db := pgtest.NewSqlxDB(t) - orm := evm.NewORM(db) + orm := evm.NewORM(db, logger.TestLogger(t), pgtest.PGCfg{}) return db, orm } @@ -107,6 +108,8 @@ func Test_EVMORM_CreateNode(t *testing.T) { require.NoError(t, err) require.Equal(t, initialCount+1, count) require.Equal(t, nodes[initialCount], node) + + assert.NoError(t, orm.DeleteChain(chain.ID)) } func Test_EVMORM_GetNodesByChainIDs(t *testing.T) { diff --git a/core/chains/evm/bulletprooftxmanager/attempts.go b/core/chains/evm/txmgr/attempts.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/attempts.go rename to core/chains/evm/txmgr/attempts.go index 7b36bc347a7..3e9ca313b1a 100644 --- a/core/chains/evm/bulletprooftxmanager/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "bytes" diff --git a/core/chains/evm/bulletprooftxmanager/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go similarity index 79% rename from core/chains/evm/bulletprooftxmanager/attempts_test.go rename to core/chains/evm/txmgr/attempts_test.go index 4fcc54842eb..8c99ff4d942 100644 --- a/core/chains/evm/bulletprooftxmanager/attempts_test.go +++ b/core/chains/evm/txmgr/attempts_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "fmt" @@ -11,8 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" "github.com/smartcontractkit/chainlink/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" @@ -20,7 +20,7 @@ import ( ksmocks "github.com/smartcontractkit/chainlink/core/services/keystore/mocks" ) -func TestBulletproofTxManager_NewDynamicFeeTx(t *testing.T) { +func TestTxm_NewDynamicFeeTx(t *testing.T) { addr := testutils.NewAddress() gcfg := cltest.NewTestGeneralConfig(t) cfg := evmtest.NewChainScopedConfig(t, gcfg) @@ -31,8 +31,8 @@ func TestBulletproofTxManager_NewDynamicFeeTx(t *testing.T) { var n int64 t.Run("creates attempt with fields", func(t *testing.T) { - cks := bulletprooftxmanager.NewChainKeyStore(*big.NewInt(1), cfg, kst) - a, err := cks.NewDynamicFeeAttempt(bulletprooftxmanager.EthTx{Nonce: &n, FromAddress: addr}, gas.DynamicFee{TipCap: assets.GWei(100), FeeCap: assets.GWei(200)}, 100) + cks := txmgr.NewChainKeyStore(*big.NewInt(1), cfg, kst) + a, err := cks.NewDynamicFeeAttempt(txmgr.EthTx{Nonce: &n, FromAddress: addr}, gas.DynamicFee{TipCap: assets.GWei(100), FeeCap: assets.GWei(200)}, 100) require.NoError(t, err) assert.Equal(t, 100, int(a.ChainSpecificGasLimit)) assert.Nil(t, a.GasPrice) @@ -72,8 +72,8 @@ func TestBulletproofTxManager_NewDynamicFeeTx(t *testing.T) { test.setCfg(gcfg) } cfg := evmtest.NewChainScopedConfig(t, gcfg) - cks := bulletprooftxmanager.NewChainKeyStore(*big.NewInt(1), cfg, kst) - _, err := cks.NewDynamicFeeAttempt(bulletprooftxmanager.EthTx{Nonce: &n, FromAddress: addr}, gas.DynamicFee{TipCap: test.tipcap, FeeCap: test.feecap}, 100) + cks := txmgr.NewChainKeyStore(*big.NewInt(1), cfg, kst) + _, err := cks.NewDynamicFeeAttempt(txmgr.EthTx{Nonce: &n, FromAddress: addr}, gas.DynamicFee{TipCap: test.tipcap, FeeCap: test.feecap}, 100) if test.expectError == "" { require.NoError(t, err) } else { @@ -85,7 +85,7 @@ func TestBulletproofTxManager_NewDynamicFeeTx(t *testing.T) { }) } -func TestBulletproofTxManager_NewLegacyAttempt(t *testing.T) { +func TestTxm_NewLegacyAttempt(t *testing.T) { addr := testutils.NewAddress() gcfg := cltest.NewTestGeneralConfig(t) cfg := evmtest.NewChainScopedConfig(t, gcfg) @@ -95,11 +95,11 @@ func TestBulletproofTxManager_NewLegacyAttempt(t *testing.T) { kst.Test(t) tx := types.NewTx(&types.LegacyTx{}) kst.On("SignTx", addr, mock.Anything, big.NewInt(1)).Return(tx, nil) - cks := bulletprooftxmanager.NewChainKeyStore(*big.NewInt(1), cfg, kst) + cks := txmgr.NewChainKeyStore(*big.NewInt(1), cfg, kst) t.Run("creates attempt with fields", func(t *testing.T) { var n int64 - a, err := cks.NewLegacyAttempt(bulletprooftxmanager.EthTx{Nonce: &n, FromAddress: addr}, big.NewInt(25), 100) + a, err := cks.NewLegacyAttempt(txmgr.EthTx{Nonce: &n, FromAddress: addr}, big.NewInt(25), 100) require.NoError(t, err) assert.Equal(t, 100, int(a.ChainSpecificGasLimit)) assert.NotNil(t, a.GasPrice) @@ -109,7 +109,7 @@ func TestBulletproofTxManager_NewLegacyAttempt(t *testing.T) { }) t.Run("verifies max gas price", func(t *testing.T) { - _, err := cks.NewLegacyAttempt(bulletprooftxmanager.EthTx{FromAddress: addr}, big.NewInt(100), 100) + _, err := cks.NewLegacyAttempt(txmgr.EthTx{FromAddress: addr}, big.NewInt(100), 100) require.Error(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("specified gas price of 100 would exceed max configured gas price of 50 for key %s", addr.Hex())) }) diff --git a/core/chains/evm/txmgr/common.go b/core/chains/evm/txmgr/common.go new file mode 100644 index 00000000000..dc1df705606 --- /dev/null +++ b/core/chains/evm/txmgr/common.go @@ -0,0 +1,82 @@ +package txmgr + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/lib/pq" + "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" + + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/core/logger" +) + +// Tries to send transactions in batches. Even if some batch(es) fail to get sent, it tries all remaining batches, +// before returning with error for the latest batch send. If a batch send fails, this sets the error on all +// elements in that batch. +func batchSendTransactions( + ctx context.Context, + db *sqlx.DB, + attempts []EthTxAttempt, + batchSize int, + logger logger.Logger, + ethClient evmclient.Client) ([]rpc.BatchElem, error) { + if len(attempts) == 0 { + return nil, nil + } + + reqs := make([]rpc.BatchElem, len(attempts)) + ethTxIDs := make([]int64, len(attempts)) + hashes := make([]common.Hash, len(attempts)) + for i, attempt := range attempts { + ethTxIDs[i] = attempt.EthTxID + hashes[i] = attempt.Hash + req := rpc.BatchElem{ + Method: "eth_sendRawTransaction", + Args: []interface{}{hexutil.Encode(attempt.SignedRawTx)}, + Result: &common.Hash{}, + } + reqs[i] = req + } + + logger.Debugw(fmt.Sprintf("Batch sending %d unconfirmed transactions.", len(attempts)), "n", len(attempts), "ethTxIDs", ethTxIDs, "hashes", hashes) + + now := time.Now() + if batchSize == 0 { + batchSize = len(reqs) + } + for i := 0; i < len(reqs); i += batchSize { + j := i + batchSize + if j > len(reqs) { + j = len(reqs) + } + + logger.Debugw(fmt.Sprintf("Batch sending transactions %v thru %v", i, j)) + + if err := ethClient.BatchCallContextAll(ctx, reqs[i:j]); err != nil { + return reqs, errors.Wrap(err, "failed to batch send transactions") + } + + if err := updateBroadcastAts(db, now, ethTxIDs[i:j]); err != nil { + return reqs, errors.Wrap(err, "failed to update last succeeded on attempts") + } + } + return reqs, nil +} + +func updateBroadcastAts(db *sqlx.DB, now time.Time, etxIDs []int64) error { + // Deliberately do nothing on NULL broadcast_at because that indicates the + // tx has been moved into a state where broadcast_at is not relevant, e.g. + // fatally errored. + // + // Since EthConfirmer/EthResender can race (totally OK since highest + // priced transaction always wins) we only want to update broadcast_at if + // our version is later. + _, err := db.Exec(`UPDATE eth_txes SET broadcast_at = $1 WHERE id = ANY($2) AND broadcast_at < $1`, now, pq.Array(etxIDs)) + return errors.Wrap(err, "updateBroadcastAts failed to update eth_txes") +} diff --git a/core/chains/evm/bulletprooftxmanager/eth_broadcaster.go b/core/chains/evm/txmgr/eth_broadcaster.go similarity index 83% rename from core/chains/evm/bulletprooftxmanager/eth_broadcaster.go rename to core/chains/evm/txmgr/eth_broadcaster.go index 86ff3d9c848..baa034d2124 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_broadcaster.go +++ b/core/chains/evm/txmgr/eth_broadcaster.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "context" @@ -122,7 +122,9 @@ func NewEthBroadcaster(db *sqlx.DB, ethClient evmclient.Client, config Config, k } } -func (eb *EthBroadcaster) Start() error { +// Start starts EthBroadcaster service. +// The provided context can be used to terminate Start sequence. +func (eb *EthBroadcaster) Start(ctx context.Context) error { return eb.StartOnce("EthBroadcaster", func() (err error) { eb.ethTxInsertListener, err = eb.eventBroadcaster.Subscribe(pg.ChannelInsertOnEthTx, "") if err != nil { @@ -130,13 +132,8 @@ func (eb *EthBroadcaster) Start() error { } if eb.config.EvmNonceAutoSync() { - ctx, cancel := utils.CombinedContext(context.Background(), eb.chStop) - defer cancel() - syncer := NewNonceSyncer(eb.db, eb.logger, eb.ChainKeyStore.config, eb.ethClient) - if ctx.Err() != nil { - return nil - } else if err := syncer.SyncAll(ctx, eb.keyStates); err != nil { + if err := syncer.SyncAll(ctx, eb.keyStates); err != nil { return errors.Wrap(err, "EthBroadcaster failed to sync with on-chain nonce") } } @@ -155,6 +152,7 @@ func (eb *EthBroadcaster) Start() error { }) } +// Close closes the EthBroadcaster func (eb *EthBroadcaster) Close() error { return eb.StopOnce("EthBroadcaster", func() error { if eb.ethTxInsertListener != nil { @@ -207,7 +205,7 @@ func (eb *EthBroadcaster) ethTxInsertTriggerer() { } func (eb *EthBroadcaster) monitorEthTxs(k ethkey.State, triggerCh chan struct{}) { - ctx, cancel := utils.CombinedContext(context.Background(), eb.chStop) + ctx, cancel := utils.ContextFromChan(eb.chStop) defer cancel() defer eb.wg.Done() @@ -238,6 +236,7 @@ func (eb *EthBroadcaster) monitorEthTxs(k ethkey.State, triggerCh chan struct{}) } } +// ProcessUnstartedEthTxs picks up and handles all eth_txes in the queue func (eb *EthBroadcaster) ProcessUnstartedEthTxs(ctx context.Context, keyState ethkey.State) error { return eb.processUnstartedEthTxs(ctx, keyState.Address.Address()) } @@ -314,7 +313,7 @@ func (eb *EthBroadcaster) processUnstartedEthTxs(ctx context.Context, fromAddres return errors.Wrap(err, "processUnstartedEthTxs failed") } - if err := eb.handleInProgressEthTx(*etx, a, time.Now()); err != nil { + if err := eb.handleInProgressEthTx(ctx, *etx, a, time.Now()); err != nil { return errors.Wrap(err, "processUnstartedEthTxs failed") } } @@ -330,7 +329,7 @@ func (eb *EthBroadcaster) handleAnyInProgressEthTx(ctx context.Context, fromAddr return errors.Wrap(err, "handleAnyInProgressEthTx failed") } if etx != nil { - if err := eb.handleInProgressEthTx(*etx, etx.EthTxAttempts[0], etx.CreatedAt); err != nil { + if err := eb.handleInProgressEthTx(ctx, *etx, etx.EthTxAttempts[0], etx.CreatedAt); err != nil { return errors.Wrap(err, "handleAnyInProgressEthTx failed") } } @@ -361,11 +360,10 @@ func getInProgressEthTx(q pg.Q, fromAddress gethCommon.Address) (etx *EthTx, err // There can be at most one in_progress transaction per address. // Here we complete the job that we didn't finish last time. -func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { +func (eb *EthBroadcaster) handleInProgressEthTx(ctx context.Context, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { if etx.State != EthTxInProgress { return errors.Errorf("invariant violation: expected transaction %v to be in_progress, it was %s", etx.ID, etx.State) } - parentCtx := context.TODO() checkerSpec, err := etx.GetChecker() if err != nil { @@ -379,7 +377,7 @@ func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, // If the transmit check does not complete within the timeout, the transaction will be sent // anyway. - checkCtx, cancel := context.WithTimeout(parentCtx, TransmitCheckTimeout) + checkCtx, cancel := context.WithTimeout(ctx, TransmitCheckTimeout) defer cancel() err = checker.Check(checkCtx, eb.logger, etx, attempt) if errors.Is(err, context.Canceled) { @@ -398,7 +396,7 @@ func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, } cancel() - sendError := sendTransaction(parentCtx, eb.ethClient, attempt, etx, eb.logger) + sendError := sendTransaction(ctx, eb.ethClient, attempt, etx, eb.logger) if sendError.IsTooExpensive() { eb.logger.Criticalw("Transaction gas price was rejected by the eth node for being too high. Consider increasing your eth node's RPCTxFeeCap (it is suggested to run geth with no cap i.e. --rpc.gascap=0 --rpc.txfeecap=0)", @@ -422,7 +420,7 @@ func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, etx.BroadcastAt = &initialBroadcastAt - if sendError.IsNonceTooLowError() || sendError.IsReplacementUnderpriced() { + if sendError.IsNonceTooLowError() || sendError.IsTransactionAlreadyMined() || sendError.IsReplacementUnderpriced() { // There are four scenarios that this can happen: // // SCENARIO 1 @@ -450,7 +448,7 @@ func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, // SCENARIO 3 // // The network/eth client can be assumed to have at-least-once delivery - // behaviour. It is possible that the eth client could have already + // behavior. It is possible that the eth client could have already // sent this exact same transaction even if this is our first time // calling SendTransaction(). // @@ -465,11 +463,12 @@ func (eb *EthBroadcaster) handleInProgressEthTx(etx EthTx, attempt EthTxAttempt, } if sendError.IsTerminallyUnderpriced() { - return eb.tryAgainBumpingGas(sendError, etx, attempt, initialBroadcastAt) + return eb.tryAgainBumpingGas(ctx, sendError, etx, attempt, initialBroadcastAt) } + // Optimism-specific cases if sendError.IsFeeTooLow() || sendError.IsFeeTooHigh() { - return eb.tryAgainWithNewEstimation(sendError, etx, attempt, initialBroadcastAt) + return eb.tryAgainWithNewEstimation(ctx, sendError, etx, attempt, initialBroadcastAt) } if sendError.IsTemporarilyUnderpriced() { @@ -593,18 +592,10 @@ func saveAttempt(q pg.Q, etx *EthTx, attempt EthTxAttempt, NewAttemptState EthTx }) } -func (eb *EthBroadcaster) tryAgainBumpingGas(sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { - if attempt.TxType == 0x2 { - return errors.New("bumping gas on initial send is not supported for EIP-1559 transactions") - } - bumpedGasPrice, bumpedGasLimit, err := eb.estimator.BumpLegacyGas(attempt.GasPrice.ToInt(), etx.GasLimit) - if err != nil { - return errors.Wrap(err, "tryAgainWithHigherGasPrice failed") - } +func (eb *EthBroadcaster) tryAgainBumpingGas(ctx context.Context, sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { eb.logger. With( "sendError", sendError, - "bumpedGasLimit", bumpedGasLimit, "attemptGasFeeCap", attempt.GasFeeCap, "attemptGasPrice", attempt.GasPrice, "attemptGasTipCap", attempt.GasTipCap, @@ -612,35 +603,79 @@ func (eb *EthBroadcaster) tryAgainBumpingGas(sendError *evmclient.SendError, etx ). Errorf("attempt gas price %v wei was rejected by the eth node for being too low. "+ "Eth node returned: '%s'. "+ - "Bumping to %v wei and retrying. ACTION REQUIRED: This is a configuration error. "+ + "Will bump and retry. ACTION REQUIRED: This is a configuration error. "+ "Consider increasing ETH_GAS_PRICE_DEFAULT (current value: %s)", - attempt.GasPrice, sendError.Error(), bumpedGasPrice, eb.config.EvmGasPriceDefault().String()) - if bumpedGasPrice.Cmp(attempt.GasPrice.ToInt()) == 0 && bumpedGasPrice.Cmp(eb.config.EvmMaxGasPriceWei()) == 0 { + attempt.GasPrice, sendError.Error(), eb.config.EvmGasPriceDefault().String()) + switch attempt.TxType { + case 0x0: + return eb.tryAgainBumpingLegacyGas(ctx, sendError, etx, attempt, initialBroadcastAt) + case 0x2: + return eb.tryAgainBumpingDynamicFeeGas(ctx, sendError, etx, attempt, initialBroadcastAt) + default: + return errors.Errorf("invariant violation: Attempt %v had unrecognised transaction type %v"+ + "This is a bug! Please report to https://github.com/smartcontractkit/chainlink/issues", attempt.ID, attempt.TxType) + } +} + +func (eb *EthBroadcaster) tryAgainBumpingLegacyGas(ctx context.Context, sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { + bumpedGasPrice, bumpedGasLimit, err := eb.estimator.BumpLegacyGas(attempt.GasPrice.ToInt(), etx.GasLimit) + if err != nil { + return errors.Wrap(err, "tryAgainBumpingLegacyGas failed") + } + if bumpedGasPrice.Cmp(attempt.GasPrice.ToInt()) == 0 || bumpedGasPrice.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 { return errors.Errorf("Hit gas price bump ceiling, will not bump further. This is a terminal error") } - return eb.tryAgainWithNewGas(etx, attempt, initialBroadcastAt, bumpedGasPrice, bumpedGasLimit) + return eb.tryAgainWithNewLegacyGas(ctx, etx, attempt, initialBroadcastAt, bumpedGasPrice, bumpedGasLimit) } -func (eb *EthBroadcaster) tryAgainWithNewEstimation(sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { +func (eb *EthBroadcaster) tryAgainBumpingDynamicFeeGas(ctx context.Context, sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { + bumpedFee, bumpedGasLimit, err := eb.estimator.BumpDynamicFee(attempt.DynamicFee(), etx.GasLimit) + if err != nil { + return errors.Wrap(err, "tryAgainBumpingDynamicFeeGas failed") + } + if bumpedFee.TipCap.Cmp(attempt.GasTipCap.ToInt()) == 0 || bumpedFee.FeeCap.Cmp(attempt.GasFeeCap.ToInt()) == 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 { + return errors.Errorf("Hit gas price bump ceiling, will not bump further. This is a terminal error") + } + return eb.tryAgainWithNewDynamicFeeGas(ctx, etx, attempt, initialBroadcastAt, bumpedFee, bumpedGasLimit) +} + +func (eb *EthBroadcaster) tryAgainWithNewEstimation(ctx context.Context, sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) error { + if attempt.TxType == 0x2 { + return errors.Errorf("AssumptionViolation: re-estimation is not supported for EIP-1559 transactions. Eth node returned error: %v. This is a bug.", sendError.Error()) + } gasPrice, gasLimit, err := eb.estimator.GetLegacyGas(etx.EncodedPayload, etx.GasLimit, gas.OptForceRefetch) if err != nil { return errors.Wrap(err, "tryAgainWithNewEstimation failed to estimate gas") } eb.logger.Debugw("Optimism rejected transaction due to incorrect fee, re-estimated and will try again", "etxID", etx.ID, "err", err, "newGasPrice", gasPrice, "newGasLimit", gasLimit) - return eb.tryAgainWithNewGas(etx, attempt, initialBroadcastAt, gasPrice, gasLimit) + return eb.tryAgainWithNewLegacyGas(ctx, etx, attempt, initialBroadcastAt, gasPrice, gasLimit) } -func (eb *EthBroadcaster) tryAgainWithNewGas(etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time, newGasPrice *big.Int, newGasLimit uint64) error { +func (eb *EthBroadcaster) tryAgainWithNewLegacyGas(ctx context.Context, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time, newGasPrice *big.Int, newGasLimit uint64) error { replacementAttempt, err := eb.NewLegacyAttempt(etx, newGasPrice, newGasLimit) if err != nil { - return errors.Wrap(err, "tryAgainWithHigherGasPrice failed") + return errors.Wrap(err, "tryAgainWithNewLegacyGas failed") + } + + if err = saveReplacementInProgressAttempt(eb.q, attempt, &replacementAttempt); err != nil { + return errors.Wrap(err, "tryAgainWithNewLegacyGas failed") + } + eb.logger.Debugw("Bumped legacy gas on initial send", "oldGasPrice", attempt.GasPrice, "newGasPrice", newGasPrice) + return eb.handleInProgressEthTx(ctx, etx, replacementAttempt, initialBroadcastAt) +} + +func (eb *EthBroadcaster) tryAgainWithNewDynamicFeeGas(ctx context.Context, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time, newDynamicFee gas.DynamicFee, newGasLimit uint64) error { + replacementAttempt, err := eb.NewDynamicFeeAttempt(etx, newDynamicFee, newGasLimit) + if err != nil { + return errors.Wrap(err, "tryAgainWithNewDynamicFeeGas failed") } if err = saveReplacementInProgressAttempt(eb.q, attempt, &replacementAttempt); err != nil { - return errors.Wrap(err, "tryAgainWithHigherGasPrice failed") + return errors.Wrap(err, "tryAgainWithNewDynamicFeeGas failed") } - return eb.handleInProgressEthTx(etx, replacementAttempt, initialBroadcastAt) + eb.logger.Debugw("Bumped dynamic fee gas on initial send", "oldFee", attempt.DynamicFee(), "newFee", newDynamicFee) + return eb.handleInProgressEthTx(ctx, etx, replacementAttempt, initialBroadcastAt) } func (eb *EthBroadcaster) saveFatallyErroredTransaction(etx *EthTx) error { diff --git a/core/chains/evm/bulletprooftxmanager/eth_broadcaster_test.go b/core/chains/evm/txmgr/eth_broadcaster_test.go similarity index 85% rename from core/chains/evm/bulletprooftxmanager/eth_broadcaster_test.go rename to core/chains/evm/txmgr/eth_broadcaster_test.go index 62f44152018..717e0c0ae0e 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_broadcaster_test.go +++ b/core/chains/evm/txmgr/eth_broadcaster_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "context" @@ -21,9 +21,9 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" gasmocks "github.com/smartcontractkit/chainlink/core/chains/evm/gas/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -41,13 +41,13 @@ import ( func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) ethClient := cltest.NewEthClientMockWithDefaultChain(t) evmcfg := evmtest.NewChainScopedConfig(t, cfg) - checkerFactory := &bulletprooftxmanager.CheckerFactory{Client: ethClient} + checkerFactory := &txmgr.CheckerFactory{Client: ethClient} eb := cltest.NewEthBroadcaster(t, db, ethClient, ethKeyStore, evmcfg, []ethkey.State{keyState}, checkerFactory) @@ -65,13 +65,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { t.Run("eth_txes exist for a different from address", func(t *testing.T) { _, otherAddress := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: otherAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -82,7 +82,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { nonce := int64(342) errStr := "some error" - etxUnconfirmed := bulletprooftxmanager.EthTx{ + etxUnconfirmed := txmgr.EthTx{ Nonce: &nonce, FromAddress: fromAddress, ToAddress: toAddress, @@ -91,9 +91,9 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { GasLimit: gasLimit, BroadcastAt: &timeNow, Error: null.String{}, - State: bulletprooftxmanager.EthTxUnconfirmed, + State: txmgr.EthTxUnconfirmed, } - etxWithError := bulletprooftxmanager.EthTx{ + etxWithError := txmgr.EthTx{ Nonce: nil, FromAddress: fromAddress, ToAddress: toAddress, @@ -101,7 +101,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { Value: value, GasLimit: gasLimit, Error: null.StringFrom(errStr), - State: bulletprooftxmanager.EthTxFatalError, + State: txmgr.EthTxFatalError, } require.NoError(t, borm.InsertEthTx(&etxUnconfirmed)) @@ -112,14 +112,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { t.Run("sends 3 EthTxs in order with higher value last, and lower values starting from the earliest", func(t *testing.T) { // Higher value - expensiveEthTx := bulletprooftxmanager.EthTx{ + expensiveEthTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 42, 0}, Value: assets.NewEthValue(242), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(2) && tx.Value().Cmp(big.NewInt(242)) == 0 @@ -127,17 +127,17 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { // Earlier tr := int32(99) - b, err := json.Marshal(bulletprooftxmanager.EthTxMeta{JobID: tr}) + b, err := json.Marshal(txmgr.EthTxMeta{JobID: tr}) require.NoError(t, err) meta := datatypes.JSON(b) - earlierEthTx := bulletprooftxmanager.EthTx{ + earlierEthTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 42, 0}, Value: value, GasLimit: gasLimit, CreatedAt: time.Unix(0, 1), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, Meta: &meta, } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -154,14 +154,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { })).Return(nil).Once() // Later - laterEthTx := bulletprooftxmanager.EthTx{ + laterEthTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 42, 1}, Value: value, GasLimit: gasLimit, CreatedAt: time.Unix(1, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { if tx.Nonce() != uint64(1) { @@ -195,7 +195,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { assert.Equal(t, int64(0), *earlierTransaction.Nonce) assert.NotNil(t, earlierTransaction.BroadcastAt) assert.Len(t, earlierTransaction.EthTxAttempts, 1) - var m bulletprooftxmanager.EthTxMeta + var m txmgr.EthTxMeta err = json.Unmarshal(*earlierEthTx.Meta, &m) require.NoError(t, err) assert.Equal(t, tr, m.JobID) @@ -210,7 +210,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { _, err = attempt.GetSignedTx() require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) require.Len(t, attempt.EthReceipts, 0) // Check laterEthTx and it's attempt @@ -232,7 +232,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { _, err = attempt.GetSignedTx() require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) require.Len(t, attempt.EthReceipts, 0) ethClient.AssertExpectations(t) @@ -245,24 +245,24 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { cfg.Overrides.GlobalEvmGasFeeCapDefault = big.NewInt(rnd + 1) cfg.Overrides.GlobalEvmMaxGasPriceWei = big.NewInt(rnd + 2) - eipTxWithoutAl := bulletprooftxmanager.EthTx{ + eipTxWithoutAl := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(142), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } - eipTxWithAl := bulletprooftxmanager.EthTx{ + eipTxWithAl := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 42, 0}, Value: assets.NewEthValue(242), GasLimit: gasLimit, CreatedAt: time.Unix(0, 1), - State: bulletprooftxmanager.EthTxUnstarted, - AccessList: bulletprooftxmanager.NullableEIP2930AccessListFrom(gethTypes.AccessList{gethTypes.AccessTuple{Address: testutils.NewAddress(), StorageKeys: []gethCommon.Hash{utils.NewHash()}}}), + State: txmgr.EthTxUnstarted, + AccessList: txmgr.NullableEIP2930AccessListFrom(gethTypes.AccessList{gethTypes.AccessTuple{Address: testutils.NewAddress(), StorageKeys: []gethCommon.Hash{utils.NewHash()}}}), } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(3) && tx.Value().Cmp(big.NewInt(142)) == 0 @@ -300,7 +300,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { _, err = attempt.GetSignedTx() require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) require.Len(t, attempt.EthReceipts, 0) }) @@ -308,16 +308,16 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { t.Run("transaction simulation", func(t *testing.T) { t.Run("when simulation succeeds, sends tx as normal", func(t *testing.T) { - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(442), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -345,21 +345,21 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { // Check ethtx was sent ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, ethTx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, ethTx.State) ethClient.AssertExpectations(t) }) t.Run("with unknown error, sends tx as normal", func(t *testing.T) { - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(542), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -375,21 +375,21 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, ethTx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, ethTx.State) ethClient.AssertExpectations(t) }) t.Run("on revert, marks tx as fatally errored and does not send", func(t *testing.T) { - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(642), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } @@ -408,7 +408,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxFatalError, ethTx.State) + assert.Equal(t, txmgr.EthTxFatalError, ethTx.State) assert.True(t, ethTx.Error.Valid) assert.Equal(t, "transaction reverted during simulation: json-rpc error { Code = 42, Message = 'oh no, it reverted', Data = 'KqYi' }", ethTx.Error.String) @@ -422,7 +422,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { func TestEthBroadcaster_TransmitChecking(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) @@ -439,16 +439,16 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Checker will return a canceled error checkerFactory.err = context.Canceled - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(442), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -461,7 +461,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Check ethtx was sent ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, ethTx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, ethTx.State) ethClient.AssertExpectations(t) }) @@ -470,16 +470,16 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Checker will return no error checkerFactory.err = nil - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(442), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -492,7 +492,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Check ethtx was sent ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, ethTx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, ethTx.State) ethClient.AssertExpectations(t) }) @@ -501,16 +501,16 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Checker will return a fatal error checkerFactory.err = errors.New("fatal checker error") - ethTx := bulletprooftxmanager.EthTx{ + ethTx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(442), GasLimit: gasLimit, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, - TransmitChecker: checkerToJson(t, bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + State: txmgr.EthTxUnstarted, + TransmitChecker: checkerToJson(t, txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }), } @@ -520,7 +520,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Check ethtx was sent ethTx, err := borm.FindEthTxWithAttempts(ethTx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxFatalError, ethTx.State) + assert.Equal(t, txmgr.EthTxFatalError, ethTx.State) assert.True(t, ethTx.Error.Valid) assert.Equal(t, "fatal checker error", ethTx.Error.String) @@ -531,7 +531,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testing.T) { // non-transactional DB needed because we deliberately test for FK violation cfg, db := heavyweight.FullTestDB(t, "eth_broadcaster_optimistic_locking", true, true) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := cltest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -546,7 +546,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi <-chBlock }) - eb := bulletprooftxmanager.NewEthBroadcaster( + eb := txmgr.NewEthBroadcaster( db, ethClient, evmcfg, @@ -559,13 +559,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi &testCheckerFactory{}, ) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{42, 42, 0}, Value: *assets.NewEth(0), GasLimit: 500000, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -592,7 +592,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) @@ -609,14 +609,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing return true })).Return(nil).Once() - tx := bulletprooftxmanager.EthTx{ + tx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411"), EncodedPayload: []byte{42, 42, 0}, Value: assets.NewEthValue(242), GasLimit: 1231, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&tx)) @@ -652,7 +652,7 @@ func TestEthBroadcaster_AssignsNonceOnStart(t *testing.T) { return account.Hex() == fromAddress.Hex() })).Return(ethNodeNonce, errors.New("something exploded")).Once() - err = eb.Start() + err = eb.Start(testutils.Context(t)) require.Error(t, err) defer eb.Close() require.Contains(t, err.Error(), "something exploded") @@ -683,7 +683,7 @@ func TestEthBroadcaster_AssignsNonceOnStart(t *testing.T) { return account.Hex() == fromAddress.Hex() })).Return(ethNodeNonce, nil).Once() - require.NoError(t, eb.Start()) + require.NoError(t, eb.Start(testutils.Context(t))) defer eb.Close() // Check keyState to make sure it has correct nonce assigned @@ -717,12 +717,12 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("cannot be more than one transaction per address in an unfinished state", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) - firstInProgress := bulletprooftxmanager.EthTx{ + firstInProgress := txmgr.EthTx{ FromAddress: fromAddress, Nonce: &firstNonce, ToAddress: toAddress, @@ -731,10 +731,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { GasLimit: gasLimit, BroadcastAt: nil, Error: null.String{}, - State: bulletprooftxmanager.EthTxInProgress, + State: txmgr.EthTxInProgress, } - secondInProgress := bulletprooftxmanager.EthTx{ + secondInProgress := txmgr.EthTx{ FromAddress: fromAddress, Nonce: &secondNonce, ToAddress: toAddress, @@ -743,7 +743,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { GasLimit: gasLimit, BroadcastAt: nil, Error: null.String{}, - State: bulletprooftxmanager.EthTxInProgress, + State: txmgr.EthTxInProgress, } require.NoError(t, borm.InsertEthTx(&firstInProgress)) @@ -754,7 +754,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("previous run assigned nonce but never broadcast", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -782,14 +782,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { assert.NotNil(t, etx.BroadcastAt) assert.False(t, etx.Error.Valid) assert.Len(t, etx.EthTxAttempts, 1) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx.EthTxAttempts[0].State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, etx.EthTxAttempts[0].State) ethClient.AssertExpectations(t) }) t.Run("previous run assigned nonce and broadcast but it fatally errored before we could save", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -824,7 +824,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("previous run assigned nonce and broadcast and is now in mempool", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -858,7 +858,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("previous run assigned nonce and broadcast and now the transaction has been confirmed", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -894,7 +894,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("previous run assigned nonce and then failed to reach node for some reason and node is still down", func(t *testing.T) { failedToReachNodeError := context.DeadlineExceeded db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -931,7 +931,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { t.Run("previous run assigned nonce and broadcast transaction then crashed and rebooted with a different configured gas price", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, nextNonce) @@ -973,14 +973,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { s, err := attempt.GetSignedTx() require.NoError(t, err) assert.Equal(t, int64(342), s.GasPrice().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) ethClient.AssertExpectations(t) }) } func getLocalNextNonce(t *testing.T, q pg.Q, fromAddress gethCommon.Address) uint64 { - n, err := bulletprooftxmanager.GetNextNonce(q, fromAddress, &cltest.FixtureChainID) + n, err := txmgr.GetNextNonce(q, fromAddress, &cltest.FixtureChainID) require.NoError(t, err) require.NotNil(t, n) return uint64(n) @@ -998,7 +998,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) q := pg.NewQ(db, logger.TestLogger(t), cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -1012,13 +1012,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { require.NoError(t, utils.JustError(db.Exec(`SET CONSTRAINTS pipeline_runs_pipeline_spec_id_fkey DEFERRED`))) t.Run("if external wallet sent a transaction from the account and now the nonce is one higher than it should be and we got replacement underpriced then we assume a previous transaction of ours was the one that succeeded, and hand off to EthConfirmer", func(t *testing.T) { - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1035,7 +1035,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // Check that the transaction was saved correctly with its attempt // We assume success and hand off to eth confirmer to eventually mark it as failed var latestID int64 - var etx1 bulletprooftxmanager.EthTx + var etx1 txmgr.EthTx require.NoError(t, db.Get(&latestID, "SELECT max(id) FROM eth_txes")) etx1, err = borm.FindEthTxWithAttempts(latestID) require.NoError(t, err) @@ -1048,7 +1048,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // Check that the local nonce was incremented by one var finalNextNonce int64 - finalNextNonce, err = bulletprooftxmanager.GetNextNonce(q, fromAddress, &cltest.FixtureChainID) + finalNextNonce, err = txmgr.GetNextNonce(q, fromAddress, &cltest.FixtureChainID) require.NoError(t, err) require.NotNil(t, finalNextNonce) require.Equal(t, int64(1), finalNextNonce) @@ -1059,13 +1059,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { localNextNonce := getLocalNextNonce(t, q, fromAddress) t.Run("without callback", func(t *testing.T) { - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1097,13 +1097,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("with callback", func(t *testing.T) { run := cltest.MustInsertPipelineRun(t, db) tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, PipelineTaskRunID: uuid.NullUUID{UUID: tr.ID, Valid: true}, } @@ -1113,7 +1113,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { return errors.New("something exploded in the callback") } - bulletprooftxmanager.SetResumeCallbackOnEthBroadcaster(fn, eb) + txmgr.SetResumeCallbackOnEthBroadcaster(fn, eb) ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce @@ -1133,7 +1133,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { return nil } - bulletprooftxmanager.SetResumeCallbackOnEthBroadcaster(fn, eb) + txmgr.SetResumeCallbackOnEthBroadcaster(fn, eb) ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce @@ -1147,19 +1147,19 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient.AssertExpectations(t) }) - bulletprooftxmanager.SetResumeCallbackOnEthBroadcaster(nil, eb) + txmgr.SetResumeCallbackOnEthBroadcaster(nil, eb) t.Run("geth Client fails with error indicating that the transaction was too expensive", func(t *testing.T) { tooExpensiveError := "tx fee (1.10 ether) exceeds the configured cap (1.00 ether)" localNextNonce := getLocalNextNonce(t, q, fromAddress) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1194,13 +1194,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { retryableErrorExample := "geth shit the bed again" localNextNonce := getLocalNextNonce(t, q, fromAddress) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1220,10 +1220,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { assert.Nil(t, etx.BroadcastAt) require.NotNil(t, etx.Nonce) assert.False(t, etx.Error.Valid) - assert.Equal(t, bulletprooftxmanager.EthTxInProgress, etx.State) + assert.Equal(t, txmgr.EthTxInProgress, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt := etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInProgress, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptInProgress, attempt.State) ethClient.AssertExpectations(t) @@ -1241,10 +1241,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { assert.NotNil(t, etx.BroadcastAt) require.NotNil(t, etx.Nonce) assert.False(t, etx.Error.Valid) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt = etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) ethClient.AssertExpectations(t) }) @@ -1256,13 +1256,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { underpricedError := "transaction underpriced" localNextNonce := getLocalNextNonce(t, q, fromAddress) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1298,13 +1298,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { assert.Equal(t, big.NewInt(30000000000).String(), attempt.GasPrice.String()) }) - etxUnfinished := bulletprooftxmanager.EthTx{ + etxUnfinished := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etxUnfinished)) @@ -1328,9 +1328,9 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { assert.Nil(t, etx.BroadcastAt) assert.NotNil(t, etx.Nonce) assert.False(t, etx.Error.Valid) - assert.Equal(t, bulletprooftxmanager.EthTxInProgress, etx.State) + assert.Equal(t, txmgr.EthTxInProgress, etx.State) assert.Len(t, etx.EthTxAttempts, 1) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInProgress, etx.EthTxAttempts[0].State) + assert.Equal(t, txmgr.EthTxAttemptInProgress, etx.EthTxAttempts[0].State) ethClient.AssertExpectations(t) }) @@ -1373,16 +1373,22 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // In this scenario the node operator REALLY fucked up and set the bump // to zero (even though that should not be possible due to config // validation) + oldGasBumpWei := evmcfg.EvmGasBumpWei() + oldGasBumpPercent := evmcfg.EvmGasBumpPercent() cfg.Overrides.GlobalEvmGasBumpWei = big.NewInt(0) cfg.Overrides.GlobalEvmGasBumpPercent = null.IntFrom(0) + defer func() { + cfg.Overrides.GlobalEvmGasBumpWei = oldGasBumpWei + cfg.Overrides.GlobalEvmGasBumpPercent = null.IntFrom(int64(oldGasBumpPercent)) + }() - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1405,13 +1411,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("eth tx is left in progress if eth node returns insufficient eth", func(t *testing.T) { insufficientEthError := "insufficient funds for transfer" localNextNonce := getLocalNextNonce(t, q, fromAddress) - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1429,10 +1435,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { assert.Nil(t, etx.BroadcastAt) require.NotNil(t, etx.Nonce) assert.False(t, etx.Error.Valid) - assert.Equal(t, bulletprooftxmanager.EthTxInProgress, etx.State) + assert.Equal(t, txmgr.EthTxInProgress, etx.State) require.Len(t, etx.EthTxAttempts, 1) attempt := etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInProgress, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptInProgress, attempt.State) assert.Nil(t, attempt.BroadcastBeforeBlockNum) ethClient.AssertExpectations(t) @@ -1441,32 +1447,94 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { pgtest.MustExec(t, db, `DELETE FROM eth_txes`) cfg.Overrides.GlobalEvmEIP1559DynamicFees = null.BoolFrom(true) - t.Run("eth node returns underpriced transaction for EIP-1559 tx, should return error", func(t *testing.T) { - // Experimentally this error is not actually possible; eth nodes will accept literally any price for EIP-1559 transactions - underpricedError := "transaction underpriced" - localNextNonce := getLocalNextNonce(t, q, fromAddress) + t.Run("eth node returns underpriced transaction and bumping gas doesn't increase it in EIP-1559 mode", func(t *testing.T) { + // This happens if a transaction's gas price is below the minimum + // configured for the transaction pool. + // This is a configuration error by the node operator, since it means they set the base gas level too low. + + // In this scenario the node operator REALLY fucked up and set the bump + // to zero (even though that should not be possible due to config + // validation) + oldGasBumpWei := evmcfg.EvmGasBumpWei() + oldGasBumpPercent := evmcfg.EvmGasBumpPercent() + cfg.Overrides.GlobalEvmGasBumpWei = big.NewInt(0) + cfg.Overrides.GlobalEvmGasBumpPercent = null.IntFrom(0) + defer func() { + cfg.Overrides.GlobalEvmGasBumpWei = oldGasBumpWei + cfg.Overrides.GlobalEvmGasBumpPercent = null.IntFrom(int64(oldGasBumpPercent)) + }() - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) - // First was underpriced + underpricedError := "transaction underpriced" + localNextNonce := getLocalNextNonce(t, q, fromAddress) ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { - return tx.Nonce() == localNextNonce + return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(1)) == 0 })).Return(errors.New(underpricedError)).Once() + // Check gas tip cap verification + err := eb.ProcessUnstartedEthTxs(context.Background(), keyState) + require.Error(t, err) + require.Contains(t, err.Error(), "bumped gas tip cap of 1 is less than or equal to original gas tip cap of 1") + + pgtest.MustExec(t, db, `DELETE FROM eth_txes`) + }) + + t.Run("eth node returns underpriced transaction in EIP-1559 mode, bumps until inclusion", func(t *testing.T) { + // This happens if a transaction's gas price is below the minimum + // configured for the transaction pool. + // This is a configuration error by the node operator, since it means they set the base gas level too low. + underpricedError := "transaction underpriced" + localNextNonce := getLocalNextNonce(t, q, fromAddress) + + etx := txmgr.EthTx{ + FromAddress: fromAddress, + ToAddress: toAddress, + EncodedPayload: encodedPayload, + Value: value, + GasLimit: gasLimit, + State: txmgr.EthTxUnstarted, + } + require.NoError(t, borm.InsertEthTx(&etx)) + + // Check gas tip cap verification + cfg.Overrides.GlobalEvmGasTipCapDefault = big.NewInt(0) err := eb.ProcessUnstartedEthTxs(context.Background(), keyState) require.Error(t, err) - assert.Contains(t, err.Error(), "bumping gas on initial send is not supported for EIP-1559 transactions") + require.Contains(t, err.Error(), "specified gas tip cap of 0 is below min configured gas tip of 1 for key") + + gasTipCapDefault := big.NewInt(42) + cfg.Overrides.GlobalEvmGasTipCapDefault = gasTipCapDefault + // Second was underpriced but above minimum + ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { + return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(gasTipCapDefault) == 0 + })).Return(errors.New(underpricedError)).Once() + // Resend at the bumped price + ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { + return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(0).Add(gasTipCapDefault, evmcfg.EvmGasBumpWei())) == 0 + })).Return(errors.New(underpricedError)).Once() + // Final bump succeeds + ethClient.On("SendTransaction", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { + return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(0).Add(gasTipCapDefault, big.NewInt(0).Mul(evmcfg.EvmGasBumpWei(), big.NewInt(2)))) == 0 + })).Return(nil).Once() + + err = eb.ProcessUnstartedEthTxs(context.Background(), keyState) + require.NoError(t, err) + + // TEARDOWN: Clear out the unsent tx before the next test + pgtest.MustExec(t, db, `DELETE FROM eth_txes WHERE nonce = $1`, localNextNonce) ethClient.AssertExpectations(t) }) + } func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { @@ -1478,7 +1546,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) realKeystore := cltest.NewKeyStore(t, db, cfg) keyState, fromAddress := cltest.MustInsertRandomKeyReturningState(t, realKeystore.Eth()) @@ -1490,13 +1558,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { eb := cltest.NewEthBroadcaster(t, db, ethClient, kst, evmcfg, []ethkey.State{keyState}, &testCheckerFactory{}) t.Run("tx signing fails", func(t *testing.T) { - etx := bulletprooftxmanager.EthTx{ + etx := txmgr.EthTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: encodedPayload, Value: value, GasLimit: gasLimit, - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } require.NoError(t, borm.InsertEthTx(&etx)) @@ -1517,7 +1585,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnstarted, etx.State) + assert.Equal(t, txmgr.EthTxUnstarted, etx.State) assert.Len(t, etx.EthTxAttempts, 0) // Check that the key did not have its nonce incremented @@ -1541,7 +1609,7 @@ func TestEthBroadcaster_GetNextNonce(t *testing.T) { keyState, _ := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) q := pg.NewQ(db, logger.TestLogger(t), cfg) - nonce, err := bulletprooftxmanager.GetNextNonce(q, keyState.Address.Address(), &cltest.FixtureChainID) + nonce, err := txmgr.GetNextNonce(q, keyState.Address.Address(), &cltest.FixtureChainID) assert.NoError(t, err) require.NotNil(t, nonce) assert.Equal(t, int64(0), nonce) @@ -1555,9 +1623,9 @@ func TestEthBroadcaster_IncrementNextNonce(t *testing.T) { keyState, _ := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) // Cannot increment if supplied nonce doesn't match existing - require.Error(t, bulletprooftxmanager.IncrementNextNonce(db, keyState.Address.Address(), &cltest.FixtureChainID, int64(42))) + require.Error(t, txmgr.IncrementNextNonce(db, keyState.Address.Address(), &cltest.FixtureChainID, int64(42))) - require.NoError(t, bulletprooftxmanager.IncrementNextNonce(db, keyState.Address.Address(), &cltest.FixtureChainID, int64(0))) + require.NoError(t, txmgr.IncrementNextNonce(db, keyState.Address.Address(), &cltest.FixtureChainID, int64(0))) // Nonce bumped to 1 require.NoError(t, db.Get(&keyState, `SELECT * FROM eth_key_states LIMIT 1`)) @@ -1584,14 +1652,14 @@ func TestEthBroadcaster_Trigger(t *testing.T) { func TestEthBroadcaster_EthTxInsertEventCausesTriggerToFire(t *testing.T) { // NOTE: Testing triggers requires committing transactions and does not work with transactional tests cfg, db := heavyweight.FullTestDB(t, "eth_tx_triggers", true, true) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) eventBroadcaster := cltest.NewEventBroadcaster(t, evmcfg.DatabaseURL()) - require.NoError(t, eventBroadcaster.Start()) + require.NoError(t, eventBroadcaster.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, eventBroadcaster.Close()) }) ethTxInsertListener, err := eventBroadcaster.Subscribe(pg.ChannelInsertOnEthTx, "") @@ -1604,7 +1672,7 @@ func TestEthBroadcaster_EthTxInsertEventCausesTriggerToFire(t *testing.T) { gomega.NewWithT(t).Eventually(ethTxInsertListener.Events()).Should(gomega.Receive()) } -func checkerToJson(t *testing.T, checker bulletprooftxmanager.TransmitCheckerSpec) *datatypes.JSON { +func checkerToJson(t *testing.T, checker txmgr.TransmitCheckerSpec) *datatypes.JSON { b, err := json.Marshal(checker) require.NoError(t, err) j := datatypes.JSON(b) @@ -1615,7 +1683,7 @@ type testCheckerFactory struct { err error } -func (t *testCheckerFactory) BuildChecker(spec bulletprooftxmanager.TransmitCheckerSpec) (bulletprooftxmanager.TransmitChecker, error) { +func (t *testCheckerFactory) BuildChecker(spec txmgr.TransmitCheckerSpec) (txmgr.TransmitChecker, error) { return &testChecker{t.err}, nil } @@ -1626,8 +1694,8 @@ type testChecker struct { func (t *testChecker) Check( _ context.Context, _ logger.Logger, - _ bulletprooftxmanager.EthTx, - _ bulletprooftxmanager.EthTxAttempt, + _ txmgr.EthTx, + _ txmgr.EthTxAttempt, ) error { return t.err } diff --git a/core/chains/evm/bulletprooftxmanager/eth_confirmer.go b/core/chains/evm/txmgr/eth_confirmer.go similarity index 91% rename from core/chains/evm/bulletprooftxmanager/eth_confirmer.go rename to core/chains/evm/txmgr/eth_confirmer.go index 6d6b3d38627..6b4e06dab56 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_confirmer.go +++ b/core/chains/evm/txmgr/eth_confirmer.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "context" @@ -21,6 +21,8 @@ import ( uuid "github.com/satori/go.uuid" "go.uber.org/multierr" + "github.com/smartcontractkit/sqlx" + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" "github.com/smartcontractkit/chainlink/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" @@ -30,7 +32,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/static" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) const ( @@ -203,6 +204,9 @@ func (ec *EthConfirmer) processHead(ctx context.Context, head *evmtypes.Head) er if err := ec.SetBroadcastBeforeBlockNum(head.Number); err != nil { return errors.Wrap(err, "SetBroadcastBeforeBlockNum failed") } + if err := ec.CheckConfirmedMissingReceipt(ctx); err != nil { + return errors.Wrap(err, "CheckConfirmedMissingReceipt failed") + } if err := ec.CheckForReceipts(ctx, head.Number); err != nil { return errors.Wrap(err, "CheckForReceipts failed") @@ -251,6 +255,65 @@ AND eth_txes.id = eth_tx_attempts.eth_tx_id AND eth_txes.evm_chain_id = $2`, return errors.Wrap(err, "SetBroadcastBeforeBlockNum failed") } +// CheckConfirmedMissingReceipt will attempt to re-send any transaction in the +// state of "confirmed_missing_receipt". If we get back any type of senderror +// other than "nonce too low" it means that this transaction isn't actually +// confirmed and needs to be put back into "unconfirmed" state, so that it can enter +// the gas bumping cycle. This is necessary in rare cases (e.g. Polygon) where +// network conditions are extremely hostile. +// +// For example, assume the following scenario: +// +// 0. We are connected to multiple primary nodes via load balancer +// 1. We send a transaction, it is confirmed and, we get a receipt +// 2. A new head comes in from RPC node 1 indicating that this transaction was re-org'd, so we put it back into unconfirmed state +// 3. We re-send that transaction to a RPC node 2 **which hasn't caught up to this re-org yet** +// 4. RPC node 2 still has an old view of the chain, so it returns us "nonce too low" indicating "no problem this transaction is already mined" +// 5. Now the transaction is marked "confirmed_missing_receipt" but the latest chain does not actually include it +// 6. Now we are reliant on the EthResender to propagate it, and this transaction will not be gas bumped, so in the event of gas spikes it could languish or even be evicted from the mempool and hold up the queue +// 7. Even if/when RPC node 2 catches up, the transaction is still stuck in state "confirmed_missing_receipt" +// +// This scenario might sound unlikely but has been observed to happen multiple times in the wild on Polygon. +func (ec *EthConfirmer) CheckConfirmedMissingReceipt(ctx context.Context) (err error) { + var attempts []EthTxAttempt + err = ec.q.Select(&attempts, + `SELECT DISTINCT ON (eth_tx_id) eth_tx_attempts.* + FROM eth_tx_attempts + JOIN eth_txes ON eth_txes.id = eth_tx_attempts.eth_tx_id AND eth_txes.state = 'confirmed_missing_receipt' + WHERE evm_chain_id = $1 + ORDER BY eth_tx_attempts.eth_tx_id ASC, eth_tx_attempts.gas_price DESC, eth_tx_attempts.gas_tip_cap DESC`, + ec.chainID.String()) + if err != nil { + return errors.Wrap(err, "CheckConfirmedMissingReceipt failed to query") + } + if len(attempts) == 0 { + return nil + } + ec.lggr.Infof("Found %d transactions confirmed_missing_receipt. The RPC node did not give us a receipt for these transactions even though it should have been mined. This could be due to using the wallet with an external account, or if the primary node is not synced or not propagating transactions properly", len(attempts)) + reqs, err := batchSendTransactions(ec.ctx, ec.db, attempts, int(ec.config.EvmRPCDefaultBatchSize()), ec.lggr, ec.ethClient) + if err != nil { + ec.lggr.Debugw("Batch sending transactions failed", err) + } + var ethTxIDsToUnconfirm []int64 + for idx, req := range reqs { + // Add to Unconfirm array, all tx where error wasn't NonceTooLow. + if req.Error != nil { + err := evmclient.NewSendError(req.Error) + if err.IsNonceTooLowError() || err.IsTransactionAlreadyMined() { + continue + } + } + + ethTxIDsToUnconfirm = append(ethTxIDsToUnconfirm, attempts[idx].EthTxID) + } + _, err = ec.q.Exec(`UPDATE eth_txes SET state='unconfirmed' WHERE id = ANY($1)`, pq.Array(ethTxIDsToUnconfirm)) + + if err != nil { + return errors.Wrap(err, "CheckConfirmedMissingReceipt: marking as unconfirmed failed") + } + return +} + // CheckForReceipts finds attempts that are still pending and checks to see if a receipt is present for the given block number func (ec *EthConfirmer) CheckForReceipts(ctx context.Context, blockNum int64) error { attempts, err := ec.findEthTxAttemptsRequiringReceiptFetch() @@ -269,12 +332,15 @@ func (ec *EthConfirmer) CheckForReceipts(ctx context.Context, blockNum int64) er } for from, attempts := range attemptsByAddress { - latestBlockNonce, err := ec.getNonceForLatestBlock(ctx, from) + minedTransactionCount, err := ec.getMinedTransactionCount(ctx, from) if err != nil { return errors.Wrapf(err, "unable to fetch pending nonce for address: %v", from) } - likelyConfirmed := ec.separateLikelyConfirmedAttempts(from, attempts, latestBlockNonce) + // separateLikelyConfirmedAttempts is used as an optimisation: there is + // no point trying to fetch receipts for attempts with a nonce higher + // than the highest nonce the RPC node thinks it has seen + likelyConfirmed := ec.separateLikelyConfirmedAttempts(from, attempts, minedTransactionCount) likelyConfirmedCount := len(likelyConfirmed) if likelyConfirmedCount > 0 { likelyUnconfirmedCount := len(attempts) - likelyConfirmedCount @@ -302,19 +368,29 @@ func (ec *EthConfirmer) CheckForReceipts(ctx context.Context, blockNum int64) er return nil } -func (ec *EthConfirmer) separateLikelyConfirmedAttempts(from gethCommon.Address, attempts []EthTxAttempt, latestBlockNonce uint64) []EthTxAttempt { +func (ec *EthConfirmer) separateLikelyConfirmedAttempts(from gethCommon.Address, attempts []EthTxAttempt, minedTransactionCount uint64) []EthTxAttempt { if len(attempts) == 0 { return attempts } - firstAttemptNonce := fmt.Sprintf("%v", *attempts[len(attempts)-1].EthTx.Nonce) - lastAttemptNonce := fmt.Sprintf("%v", *attempts[0].EthTx.Nonce) - ec.lggr.Debugw(fmt.Sprintf("There are %v attempts from address %v, latest nonce for it is %v and for the attempts' nonces: first = %v, last = %v", - len(attempts), from, latestBlockNonce, firstAttemptNonce, lastAttemptNonce), "nAttempts", len(attempts), "fromAddress", from, "latestBlockNonce", latestBlockNonce, "firstAttemptNonce", firstAttemptNonce, "lastAttemptNonce", lastAttemptNonce) + firstAttemptNonce := *attempts[len(attempts)-1].EthTx.Nonce + lastAttemptNonce := *attempts[0].EthTx.Nonce + latestMinedNonce := int64(minedTransactionCount) - 1 // this can be -1 if a transaction has never been mined on this account + ec.lggr.Debugw(fmt.Sprintf("There are %d attempts from address %s, mined transaction count is %d (latest mined nonce is %d) and for the attempts' nonces: first = %d, last = %d", + len(attempts), from.Hex(), minedTransactionCount, latestMinedNonce, firstAttemptNonce, lastAttemptNonce), "nAttempts", len(attempts), "fromAddress", from, "minedTransactionCount", minedTransactionCount, "latestMinedNonce", latestMinedNonce, "firstAttemptNonce", firstAttemptNonce, "lastAttemptNonce", lastAttemptNonce) likelyConfirmed := attempts + // attempts are ordered by nonce ASC for i := 0; i < len(attempts); i++ { - if attempts[i].EthTx.Nonce != nil && *attempts[i].EthTx.Nonce > int64(latestBlockNonce) { + // If the attempt nonce is lower or equal to the latestBlockNonce + // it must have been confirmed, we just didn't get a receipt yet + // + // Examples: + // 3 transactions confirmed, highest has nonce 2 + // 5 total attempts, highest has nonce 4 + // minedTransactionCount=3 + // likelyConfirmed will be attempts[0:3] which gives the first 3 transactions, as expected + if *attempts[i].EthTx.Nonce > int64(minedTransactionCount) { ec.lggr.Debugf("Marking attempts as likely confirmed just before index %v, at nonce: %v", i, *attempts[i].EthTx.Nonce) likelyConfirmed = attempts[0:i] break @@ -374,7 +450,7 @@ ORDER BY eth_txes.nonce ASC, eth_tx_attempts.gas_price DESC, eth_tx_attempts.gas return } -func (ec *EthConfirmer) getNonceForLatestBlock(ctx context.Context, from gethCommon.Address) (nonce uint64, err error) { +func (ec *EthConfirmer) getMinedTransactionCount(ctx context.Context, from gethCommon.Address) (nonce uint64, err error) { return ec.ethClient.NonceAt(ctx, from, nil) } @@ -408,30 +484,31 @@ func (ec *EthConfirmer) batchFetchReceipts(ctx context.Context, attempts []EthTx } l := lggr.With( - "txHash", attempt.Hash.Hex(), "ethTxAttemptID", attempt.ID, "ethTxID", attempt.EthTxID, "err", err, + "txHash", attempt.Hash.Hex(), "ethTxAttemptID", attempt.ID, + "ethTxID", attempt.EthTxID, "err", err, "nonce", attempt.EthTx.Nonce, ) if err != nil { - l.Errorw("FetchReceipt failed") + l.Error("FetchReceipt failed") continue } if receipt == nil { - // NOTE: This should never possibly happen, but it seems safer to - // check regardless to avoid a potential panic - l.Errorw("Invariant violation, got nil receipt") + // NOTE: This should never happen, but it seems safer to check + // regardless to avoid a potential panic + l.Error("AssumptionViolation: got nil receipt") continue } if receipt.IsZero() { - l.Debugw("Still waiting for receipt") + l.Debug("Still waiting for receipt") continue } l = l.With("blockHash", receipt.BlockHash.Hex(), "status", receipt.Status, "transactionIndex", receipt.TransactionIndex) if receipt.IsUnmined() { - l.Debugw("Got receipt for transaction but it's still in the mempool and not included in a block yet") + l.Debug("Got receipt for transaction but it's still in the mempool and not included in a block yet") continue } @@ -965,7 +1042,7 @@ func (ec *EthConfirmer) bumpGas(previousAttempt EthTxAttempt) (bumpedAttempt Eth "This is a bug! Please report to https://github.com/smartcontractkit/chainlink/issues", previousAttempt.ID, previousAttempt.TxType) } - if errors.Cause(err) == gas.ErrBumpGasExceedsLimit { + if errors.Is(errors.Cause(err), gas.ErrBumpGasExceedsLimit) { promGasBumpExceedsLimit.WithLabelValues(ec.chainID.String()).Inc() } @@ -1077,7 +1154,7 @@ func (ec *EthConfirmer) handleInProgressAttempt(ctx context.Context, etx EthTx, return deleteInProgressAttempt(ec.q.WithOpts(pg.WithParentCtx(ctx)), attempt) } - if sendError.IsNonceTooLowError() { + if sendError.IsNonceTooLowError() || sendError.IsTransactionAlreadyMined() { // Nonce too low indicated that a transaction at this nonce was confirmed already. // Mark confirmed_missing_receipt and wait for the next cycle to try to get a receipt sendError = nil diff --git a/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go b/core/chains/evm/txmgr/eth_confirmer_test.go similarity index 77% rename from core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go rename to core/chains/evm/txmgr/eth_confirmer_test.go index f62eab71db8..706511d8898 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go +++ b/core/chains/evm/txmgr/eth_confirmer_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "context" @@ -21,10 +21,11 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -40,15 +41,15 @@ func newTestChainScopedConfig(t *testing.T) evmconfig.ChainScopedConfig { return evmtest.NewChainScopedConfig(t, cfg) } -func mustInsertUnstartedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, fromAddress gethCommon.Address) { +func mustInsertUnstartedEthTx(t *testing.T, borm txmgr.ORM, fromAddress gethCommon.Address) { etx := cltest.NewEthTx(t, fromAddress) - etx.State = bulletprooftxmanager.EthTxUnstarted + etx.State = txmgr.EthTxUnstarted require.NoError(t, borm.InsertEthTx(&etx)) } -func newBroadcastLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int64) bulletprooftxmanager.EthTxAttempt { +func newBroadcastLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int64) txmgr.EthTxAttempt { attempt := cltest.NewLegacyEthTxAttempt(t, etxID) - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast if len(gasPrice) > 0 { gp := gasPrice[0] attempt.GasPrice = utils.NewBig(big.NewInt(gp)) @@ -56,9 +57,9 @@ func newBroadcastLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int64 return attempt } -func newInProgressLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int64) bulletprooftxmanager.EthTxAttempt { +func newInProgressLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int64) txmgr.EthTxAttempt { attempt := cltest.NewLegacyEthTxAttempt(t, etxID) - attempt.State = bulletprooftxmanager.EthTxAttemptInProgress + attempt.State = txmgr.EthTxAttemptInProgress if len(gasPrice) > 0 { gp := gasPrice[0] attempt.GasPrice = utils.NewBig(big.NewInt(gp)) @@ -66,18 +67,18 @@ func newInProgressLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int6 return attempt } -func mustInsertInProgressEthTx(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress gethCommon.Address) bulletprooftxmanager.EthTx { +func mustInsertInProgressEthTx(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress gethCommon.Address) txmgr.EthTx { etx := cltest.NewEthTx(t, fromAddress) - etx.State = bulletprooftxmanager.EthTxInProgress + etx.State = txmgr.EthTxInProgress etx.Nonce = &nonce require.NoError(t, borm.InsertEthTx(&etx)) return etx } -func mustInsertConfirmedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress gethCommon.Address) bulletprooftxmanager.EthTx { +func mustInsertConfirmedEthTx(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress gethCommon.Address) txmgr.EthTx { etx := cltest.NewEthTx(t, fromAddress) - etx.State = bulletprooftxmanager.EthTxConfirmed + etx.State = txmgr.EthTxConfirmed etx.Nonce = &nonce now := time.Now() etx.BroadcastAt = &now @@ -90,7 +91,7 @@ func TestEthConfirmer_SetBroadcastBeforeBlockNum(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := newTestChainScopedConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() ethClient := cltest.NewEthClientMockWithDefaultChain(t) state, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) @@ -156,7 +157,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { db := pgtest.NewSqlxDB(t) config := newTestChainScopedConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, config) + borm := cltest.NewTxmORM(t, db, config) ethClient := cltest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, config).Eth() @@ -196,7 +197,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) // Transaction not confirmed yet, receipt is nil ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt1_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt1_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &evmtypes.Receipt{} @@ -217,7 +218,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }) t.Run("saves nothing if returned receipt does not match the attempt", func(t *testing.T) { - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: utils.NewHash(), BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -227,10 +228,10 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) // First transaction confirmed ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt1_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt1_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bptxmReceipt + elems[0].Result = &txmReceipt }).Once() // No error because it is merely logged @@ -244,7 +245,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }) t.Run("saves nothing if query returns error", func(t *testing.T) { - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt1_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -254,10 +255,10 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) // First transaction confirmed ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt1_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt1_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bptxmReceipt + elems[0].Result = &txmReceipt elems[0].Error = errors.New("foo") }).Once() @@ -277,7 +278,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.Len(t, attempt2_1.EthReceipts, 0) t.Run("saves eth_receipt and marks eth_tx as confirmed when geth client returns valid receipt", func(t *testing.T) { - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt1_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -287,13 +288,13 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempt1_1.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt2_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt1_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt2_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // First transaction confirmed - elems[0].Result = &bptxmReceipt + elems[0].Result = &txmReceipt // Second transaction still unconfirmed elems[1].Result = &evmtypes.Receipt{} }).Once() @@ -305,19 +306,19 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { etx, err := borm.FindEthTxWithAttempts(etx1.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + assert.Equal(t, txmgr.EthTxConfirmed, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt1_1 = etx.EthTxAttempts[0] require.Len(t, attempt1_1.EthReceipts, 1) ethReceipt := attempt1_1.EthReceipts[0] - assert.Equal(t, bptxmReceipt.TxHash, ethReceipt.TxHash) - assert.Equal(t, bptxmReceipt.BlockHash, ethReceipt.BlockHash) - assert.Equal(t, bptxmReceipt.BlockNumber.Int64(), ethReceipt.BlockNumber) - assert.Equal(t, bptxmReceipt.TransactionIndex, ethReceipt.TransactionIndex) + assert.Equal(t, txmReceipt.TxHash, ethReceipt.TxHash) + assert.Equal(t, txmReceipt.BlockHash, ethReceipt.BlockHash) + assert.Equal(t, txmReceipt.BlockNumber.Int64(), ethReceipt.BlockNumber) + assert.Equal(t, txmReceipt.TransactionIndex, ethReceipt.TransactionIndex) - receiptJSON, err := json.Marshal(bptxmReceipt) + receiptJSON, err := json.Marshal(txmReceipt) require.NoError(t, err) assert.JSONEq(t, string(receiptJSON), string(ethReceipt.Receipt)) @@ -336,7 +337,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt2_3)) require.NoError(t, borm.InsertEthTxAttempt(&attempt2_2)) - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt2_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -346,16 +347,16 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 3 && - cltest.BatchElemMatchesHash(b[2], attempt2_1.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt2_2.Hash) && - cltest.BatchElemMatchesHash(b[0], attempt2_3.Hash) + cltest.BatchElemMatchesParams(b[2], attempt2_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt2_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[0], attempt2_3.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // Most expensive attempt still unconfirmed elems[2].Result = &evmtypes.Receipt{} // Second most expensive attempt is confirmed - elems[1].Result = &bptxmReceipt + elems[1].Result = &txmReceipt // Cheapest attempt still unconfirmed elems[0].Result = &evmtypes.Receipt{} }).Once() @@ -369,7 +370,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { etx, err := borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + require.Equal(t, txmgr.EthTxConfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 3) }) @@ -383,7 +384,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { TxHash: attempt3_1.Hash, } ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt3_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt3_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &receipt @@ -396,7 +397,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { etx, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt3_1 = etx.EthTxAttempts[0] require.Len(t, attempt3_1.EthReceipts, 0) @@ -409,7 +410,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { BlockHash: utils.NewHash(), } ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt3_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt3_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &receipt @@ -422,7 +423,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { etx, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt3_1 = etx.EthTxAttempts[0] require.Len(t, attempt3_1.EthReceipts, 0) @@ -431,7 +432,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { t.Run("handles case where eth_receipt already exists somehow", func(t *testing.T) { ethReceipt := cltest.MustInsertEthReceipt(t, borm, 42, utils.NewHash(), attempt3_1.Hash) - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: ethReceipt.BlockHash, BlockNumber: big.NewInt(ethReceipt.BlockNumber), @@ -439,10 +440,10 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { } ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { - return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt3_1.Hash) + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempt3_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bptxmReceipt + elems[0].Result = &txmReceipt }).Once() // Do the thing @@ -452,17 +453,17 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { etx, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + assert.Equal(t, txmgr.EthTxConfirmed, etx.State) assert.Len(t, etx.EthTxAttempts, 1) attempt3_1 = etx.EthTxAttempts[0] require.Len(t, attempt3_1.EthReceipts, 1) ethReceipt = attempt3_1.EthReceipts[0] - assert.Equal(t, bptxmReceipt.TxHash, ethReceipt.TxHash) - assert.Equal(t, bptxmReceipt.BlockHash, ethReceipt.BlockHash) - assert.Equal(t, bptxmReceipt.BlockNumber.Int64(), ethReceipt.BlockNumber) - assert.Equal(t, bptxmReceipt.TransactionIndex, ethReceipt.TransactionIndex) + assert.Equal(t, txmReceipt.TxHash, ethReceipt.TxHash) + assert.Equal(t, txmReceipt.BlockHash, ethReceipt.BlockHash) + assert.Equal(t, txmReceipt.BlockNumber.Int64(), ethReceipt.BlockNumber) + assert.Equal(t, txmReceipt.TransactionIndex, ethReceipt.TransactionIndex) ethClient.AssertExpectations(t) }) @@ -477,7 +478,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt4_2)) - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt4_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -487,14 +488,14 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { // Second attempt is confirmed ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempt4_2.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt4_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt4_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt4_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // First attempt still unconfirmed elems[1].Result = &evmtypes.Receipt{} // Second attempt is confirmed - elems[0].Result = &bptxmReceipt + elems[0].Result = &txmReceipt }).Once() // Do the thing @@ -511,9 +512,9 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { attempt4_2 = etx4.EthTxAttempts[0] // And the attempts - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt4_1.State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, attempt4_1.State) require.Nil(t, attempt4_1.BroadcastBeforeBlockNum) - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt4_2.State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, attempt4_2.State) require.Equal(t, int64(42), *attempt4_2.BroadcastBeforeBlockNum) // Check receipts @@ -528,7 +529,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) cfg.Overrides.GlobalEvmRPCDefaultBatchSize = null.IntFrom(2) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -543,7 +544,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { ctx := context.Background() etx := cltest.MustInsertUnconfirmedEthTx(t, borm, 0, fromAddress) - var attempts []bulletprooftxmanager.EthTxAttempt + var attempts []txmgr.EthTxAttempt // Total of 5 attempts should lead to 3 batched fetches (2, 2, 1) for i := 0; i < 5; i++ { @@ -556,8 +557,8 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempts[4].Hash) && - cltest.BatchElemMatchesHash(b[1], attempts[3].Hash) + cltest.BatchElemMatchesParams(b[0], attempts[4].Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempts[3].Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &evmtypes.Receipt{} @@ -565,8 +566,8 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { }).Once() ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempts[2].Hash) && - cltest.BatchElemMatchesHash(b[1], attempts[1].Hash) + cltest.BatchElemMatchesParams(b[0], attempts[2].Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempts[1].Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &evmtypes.Receipt{} @@ -574,7 +575,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { }).Once() ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 1 && - cltest.BatchElemMatchesHash(b[0], attempts[0].Hash) + cltest.BatchElemMatchesParams(b[0], attempts[0].Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) elems[0].Result = &evmtypes.Receipt{} @@ -590,7 +591,7 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) cfg.Overrides.GlobalEvmRPCDefaultBatchSize = null.IntFrom(6) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -604,7 +605,7 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { ctx := context.Background() - var attempts []bulletprooftxmanager.EthTxAttempt + var attempts []txmgr.EthTxAttempt // inserting in DESC nonce order to test DB ASC ordering etx2 := cltest.MustInsertUnconfirmedEthTx(t, borm, 1, fromAddress) for i := 0; i < 4; i++ { @@ -636,10 +637,10 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { require.NoError(t, ec.CheckForReceipts(ctx, 42)) - cltest.BatchElemMustMatchHash(t, captured[0], attempts[0].Hash) - cltest.BatchElemMustMatchHash(t, captured[1], attempts[1].Hash) - cltest.BatchElemMustMatchHash(t, captured[2], attempts[2].Hash) - cltest.BatchElemMustMatchHash(t, captured[3], attempts[3].Hash) + cltest.BatchElemMustMatchParams(t, captured[0], attempts[0].Hash, "eth_getTransactionReceipt") + cltest.BatchElemMustMatchParams(t, captured[1], attempts[1].Hash, "eth_getTransactionReceipt") + cltest.BatchElemMustMatchParams(t, captured[2], attempts[2].Hash, "eth_getTransactionReceipt") + cltest.BatchElemMustMatchParams(t, captured[3], attempts[3].Hash, "eth_getTransactionReceipt") ethClient.AssertExpectations(t) } @@ -649,7 +650,7 @@ func TestEthConfirmer_CheckForReceipts_should_not_check_for_likely_unconfirmed(t db := pgtest.NewSqlxDB(t) config := newTestChainScopedConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, config) + borm := cltest.NewTxmORM(t, db, config) ethKeyStore := cltest.NewKeyStore(t, db, config).Eth() @@ -681,7 +682,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -729,13 +730,13 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { pgtest.MustExec(t, db, `UPDATE eth_tx_attempts SET broadcast_before_block_num = 41 WHERE broadcast_before_block_num IS NULL`) t.Run("marks buried eth_txes as 'confirmed_missing_receipt'", func(t *testing.T) { - bptxmReceipt0 := evmtypes.Receipt{ + txmReceipt0 := evmtypes.Receipt{ TxHash: attempt0_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), TransactionIndex: uint(1), } - bptxmReceipt3 := evmtypes.Receipt{ + txmReceipt3 := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -744,17 +745,17 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(4), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 6 && - cltest.BatchElemMatchesHash(b[0], attempt0_2.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt0_1.Hash) && - cltest.BatchElemMatchesHash(b[2], attempt1_2.Hash) && - cltest.BatchElemMatchesHash(b[3], attempt1_1.Hash) && - cltest.BatchElemMatchesHash(b[4], attempt2_1.Hash) && - cltest.BatchElemMatchesHash(b[5], attempt3_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt0_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt0_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[2], attempt1_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[3], attempt1_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[4], attempt2_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[5], attempt3_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // First transaction confirmed - elems[0].Result = &bptxmReceipt0 + elems[0].Result = &txmReceipt0 elems[1].Result = &evmtypes.Receipt{} // Second transaction stil unconfirmed elems[2].Result = &evmtypes.Receipt{} @@ -762,7 +763,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // Third transaction still unconfirmed elems[4].Result = &evmtypes.Receipt{} // Fourth transaction is confirmed - elems[5].Result = &bptxmReceipt3 + elems[5].Result = &txmReceipt3 }).Once() // PERFORM @@ -775,26 +776,26 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // two below it "confirmed_missing_receipt" and the "bottom" eth_tx also confirmed etx3, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx3.State) + require.Equal(t, txmgr.EthTxConfirmed, etx3.State) ethReceipt := etx3.EthTxAttempts[0].EthReceipts[0] - require.Equal(t, bptxmReceipt3.BlockHash, ethReceipt.BlockHash) + require.Equal(t, txmReceipt3.BlockHash, ethReceipt.BlockHash) etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx2.State) + require.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx2.State) etx1, err = borm.FindEthTxWithAttempts(etx1.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx1.State) + require.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx1.State) etx0, err = borm.FindEthTxWithAttempts(etx0.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx0.State) + require.Equal(t, txmgr.EthTxConfirmed, etx0.State) require.Len(t, etx0.EthTxAttempts, 2) require.Len(t, etx0.EthTxAttempts[0].EthReceipts, 1) ethReceipt = etx0.EthTxAttempts[0].EthReceipts[0] - require.Equal(t, bptxmReceipt0.BlockHash, ethReceipt.BlockHash) + require.Equal(t, txmReceipt0.BlockHash, ethReceipt.BlockHash) }) // STATE @@ -804,7 +805,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // eth_txes with nonce 3 is confirmed t.Run("marks eth_txes with state 'confirmed_missing_receipt' as 'confirmed' if a receipt finally shows up", func(t *testing.T) { - bptxmReceipt := evmtypes.Receipt{ + txmReceipt := evmtypes.Receipt{ TxHash: attempt2_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(43), @@ -813,9 +814,9 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 3 && - cltest.BatchElemMatchesHash(b[0], attempt1_2.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt1_1.Hash) && - cltest.BatchElemMatchesHash(b[2], attempt2_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt1_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt1_1.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[2], attempt2_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) @@ -823,7 +824,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { elems[0].Result = &evmtypes.Receipt{} elems[1].Result = &evmtypes.Receipt{} // Second transaction confirmed - elems[2].Result = &bptxmReceipt + elems[2].Result = &txmReceipt }).Once() // PERFORM @@ -836,20 +837,20 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // one below it still "confirmed_missing_receipt" and the bottom one remains confirmed etx3, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx3.State) + require.Equal(t, txmgr.EthTxConfirmed, etx3.State) etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx2.State) + require.Equal(t, txmgr.EthTxConfirmed, etx2.State) ethReceipt := etx2.EthTxAttempts[0].EthReceipts[0] - require.Equal(t, bptxmReceipt.BlockHash, ethReceipt.BlockHash) + require.Equal(t, txmReceipt.BlockHash, ethReceipt.BlockHash) etx1, err = borm.FindEthTxWithAttempts(etx1.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx1.State) + require.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx1.State) etx0, err = borm.FindEthTxWithAttempts(etx0.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx0.State) + require.Equal(t, txmgr.EthTxConfirmed, etx0.State) }) // STATE @@ -862,8 +863,8 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempt1_2.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt1_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt1_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt1_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) @@ -882,16 +883,16 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // one below it still "confirmed_missing_receipt" and the bottom one remains confirmed etx3, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx3.State) + require.Equal(t, txmgr.EthTxConfirmed, etx3.State) etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx2.State) + require.Equal(t, txmgr.EthTxConfirmed, etx2.State) etx1, err = borm.FindEthTxWithAttempts(etx1.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx1.State) + require.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx1.State) etx0, err = borm.FindEthTxWithAttempts(etx0.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx0.State) + require.Equal(t, txmgr.EthTxConfirmed, etx0.State) }) // STATE @@ -904,8 +905,8 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - cltest.BatchElemMatchesHash(b[0], attempt1_2.Hash) && - cltest.BatchElemMatchesHash(b[1], attempt1_1.Hash) + cltest.BatchElemMatchesParams(b[0], attempt1_2.Hash, "eth_getTransactionReceipt") && + cltest.BatchElemMatchesParams(b[1], attempt1_1.Hash, "eth_getTransactionReceipt") })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) @@ -924,25 +925,240 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // one below it marked as "fatal_error" and the bottom one remains confirmed etx3, err := borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx3.State) + require.Equal(t, txmgr.EthTxConfirmed, etx3.State) etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx2.State) + require.Equal(t, txmgr.EthTxConfirmed, etx2.State) etx1, err = borm.FindEthTxWithAttempts(etx1.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxFatalError, etx1.State) + require.Equal(t, txmgr.EthTxFatalError, etx1.State) etx0, err = borm.FindEthTxWithAttempts(etx0.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx0.State) + require.Equal(t, txmgr.EthTxConfirmed, etx0.State) }) } +func TestEthConfirmer_CheckConfirmedMissingReceipt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + borm := cltest.NewTxmORM(t, db, cfg) + + ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() + + state, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) + + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + cfg.Overrides.GlobalEvmFinalityDepth = null.IntFrom(50) + evmcfg := evmtest.NewChainScopedConfig(t, cfg) + + ec := cltest.NewEthConfirmer(t, db, ethClient, evmcfg, ethKeyStore, []ethkey.State{state}, nil) + + ctx := context.Background() + + // STATE + // eth_txes with nonce 0 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 1 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 2 has one attempt + originalBroadcastAt := time.Unix(1616509100, 0) + etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 0, 1, originalBroadcastAt, fromAddress) + attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt0_2)) + etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 1, 1, originalBroadcastAt, fromAddress) + attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt1_2)) + etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 2, 1, originalBroadcastAt, fromAddress) + attempt2_1 := etx2.EthTxAttempts[0] + etx3 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 3, 1, originalBroadcastAt, fromAddress) + attempt3_1 := etx3.EthTxAttempts[0] + + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 4 && + cltest.BatchElemMatchesParams(b[0], hexutil.Encode(attempt0_2.SignedRawTx), "eth_sendRawTransaction") && + cltest.BatchElemMatchesParams(b[1], hexutil.Encode(attempt1_2.SignedRawTx), "eth_sendRawTransaction") && + cltest.BatchElemMatchesParams(b[2], hexutil.Encode(attempt2_1.SignedRawTx), "eth_sendRawTransaction") && + cltest.BatchElemMatchesParams(b[3], hexutil.Encode(attempt3_1.SignedRawTx), "eth_sendRawTransaction") + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + // First transaction confirmed + elems[0].Error = errors.New("nonce too low") + elems[1].Error = errors.New("transaction underpriced") + elems[2].Error = nil + elems[3].Error = errors.New("transaction already finalized") + }).Once() + + // PERFORM + require.NoError(t, ec.CheckConfirmedMissingReceipt(ctx)) + + ethClient.AssertExpectations(t) + + // Expected state is that the "top" eth_tx is untouched but the other two + // are marked as unconfirmed + etx0, err := borm.FindEthTxWithAttempts(etx0.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx0.State) + assert.Greater(t, etx0.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx1, err = borm.FindEthTxWithAttempts(etx1.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx1.State) + assert.Greater(t, etx1.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx2, err = borm.FindEthTxWithAttempts(etx2.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx2.State) + assert.Greater(t, etx2.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx3, err = borm.FindEthTxWithAttempts(etx3.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx3.State) + assert.Greater(t, etx3.BroadcastAt.Unix(), originalBroadcastAt.Unix()) +} + +func TestEthConfirmer_CheckConfirmedMissingReceipt_batchSendTransactions_fails(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + borm := cltest.NewTxmORM(t, db, cfg) + + ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() + + state, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) + + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + cfg.Overrides.GlobalEvmFinalityDepth = null.IntFrom(50) + evmcfg := evmtest.NewChainScopedConfig(t, cfg) + + ec := cltest.NewEthConfirmer(t, db, ethClient, evmcfg, ethKeyStore, []ethkey.State{state}, nil) + + ctx := context.Background() + + // STATE + // eth_txes with nonce 0 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 1 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 2 has one attempt + originalBroadcastAt := time.Unix(1616509100, 0) + etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 0, 1, originalBroadcastAt, fromAddress) + attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt0_2)) + etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 1, 1, originalBroadcastAt, fromAddress) + attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt1_2)) + etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 2, 1, originalBroadcastAt, fromAddress) + attempt2_1 := etx2.EthTxAttempts[0] + + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 3 && + cltest.BatchElemMatchesParams(b[0], hexutil.Encode(attempt0_2.SignedRawTx), "eth_sendRawTransaction") && + cltest.BatchElemMatchesParams(b[1], hexutil.Encode(attempt1_2.SignedRawTx), "eth_sendRawTransaction") && + cltest.BatchElemMatchesParams(b[2], hexutil.Encode(attempt2_1.SignedRawTx), "eth_sendRawTransaction") + })).Return(errors.New("Timed out")).Once() + + // PERFORM + require.NoError(t, ec.CheckConfirmedMissingReceipt(ctx)) + + ethClient.AssertExpectations(t) + + // Expected state is that all txes are marked as unconfirmed, since the batch call had failed + etx0, err := borm.FindEthTxWithAttempts(etx0.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx0.State) + assert.Equal(t, etx0.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx1, err = borm.FindEthTxWithAttempts(etx1.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx1.State) + assert.Equal(t, etx1.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx2, err = borm.FindEthTxWithAttempts(etx2.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx2.State) + assert.Equal(t, etx2.BroadcastAt.Unix(), originalBroadcastAt.Unix()) +} + +func TestEthConfirmer_CheckConfirmedMissingReceipt_smallEvmRPCBatchSize_middleBatchSendTransactionFails(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + borm := cltest.NewTxmORM(t, db, cfg) + + ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() + + state, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) + + ethClient := cltest.NewEthClientMockWithDefaultChain(t) + + cfg.Overrides.GlobalEvmFinalityDepth = null.IntFrom(50) + cfg.Overrides.GlobalEvmRPCDefaultBatchSize = null.IntFrom(1) // Set default batch size to 1 + evmcfg := evmtest.NewChainScopedConfig(t, cfg) + + ec := cltest.NewEthConfirmer(t, db, ethClient, evmcfg, ethKeyStore, []ethkey.State{state}, nil) + + ctx := context.Background() + + // STATE + // eth_txes with nonce 0 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 1 has two attempts, the later attempt with higher gas fees + // eth_txes with nonce 2 has one attempt + originalBroadcastAt := time.Unix(1616509100, 0) + etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 0, 1, originalBroadcastAt, fromAddress) + attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt0_2)) + etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 1, 1, originalBroadcastAt, fromAddress) + attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) + require.NoError(t, borm.InsertEthTxAttempt(&attempt1_2)) + etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, borm, 2, 1, originalBroadcastAt, fromAddress) + + // Expect eth_sendRawTransaction in 3 batches. First batch will pass, 2nd will fail, 3rd never attempted. + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && + cltest.BatchElemMatchesParams(b[0], hexutil.Encode(attempt0_2.SignedRawTx), "eth_sendRawTransaction") + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + // First transaction confirmed + elems[0].Error = errors.New("nonce too low") + }).Once() + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && + cltest.BatchElemMatchesParams(b[0], hexutil.Encode(attempt1_2.SignedRawTx), "eth_sendRawTransaction") + })).Return(errors.New("Timed out")).Once() + + // PERFORM + require.NoError(t, ec.CheckConfirmedMissingReceipt(ctx)) + + ethClient.AssertExpectations(t) + + // Expected state is that all transactions since failed batch will be unconfirmed + etx0, err := borm.FindEthTxWithAttempts(etx0.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx0.State) + assert.Greater(t, etx0.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx1, err = borm.FindEthTxWithAttempts(etx1.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx1.State) + assert.Equal(t, etx1.BroadcastAt.Unix(), originalBroadcastAt.Unix()) + etx2, err = borm.FindEthTxWithAttempts(etx2.ID) + assert.NoError(t, err) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx2.State) + assert.Equal(t, etx2.BroadcastAt.Unix(), originalBroadcastAt.Unix()) +} + func TestEthConfirmer_FindEthTxsRequiringResubmissionDueToInsufficientEth(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) q := pg.NewQ(db, logger.TestLogger(t), cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -954,7 +1170,7 @@ func TestEthConfirmer_FindEthTxsRequiringResubmissionDueToInsufficientEth(t *tes etx2 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, borm, 1, fromAddress) etx3 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 2, fromAddress) attempt3_2 := cltest.NewLegacyEthTxAttempt(t, etx3.ID) - attempt3_2.State = bulletprooftxmanager.EthTxAttemptInsufficientEth + attempt3_2.State = txmgr.EthTxAttemptInsufficientEth attempt3_2.GasPrice = utils.NewBig(big.NewInt(100)) require.NoError(t, borm.InsertEthTxAttempt(&attempt3_2)) etx1 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, borm, 0, fromAddress) @@ -965,7 +1181,7 @@ func TestEthConfirmer_FindEthTxsRequiringResubmissionDueToInsufficientEth(t *tes cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, borm, 0, otherAddress) t.Run("returns all eth_txes with at least one attempt that is in insufficient_eth state", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 3) @@ -979,7 +1195,7 @@ func TestEthConfirmer_FindEthTxsRequiringResubmissionDueToInsufficientEth(t *tes }) t.Run("does not return eth_txes with different chain ID", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, *big.NewInt(42)) + etxs, err := txmgr.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, *big.NewInt(42)) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -989,7 +1205,7 @@ func TestEthConfirmer_FindEthTxsRequiringResubmissionDueToInsufficientEth(t *tes pgtest.MustExec(t, db, `UPDATE eth_txes SET state='confirmed' WHERE id = $1`, etx1.ID) pgtest.MustExec(t, db, `UPDATE eth_txes SET state='fatal_error', nonce=NULL, error='foo', broadcast_at=NULL WHERE id = $1`, etx2.ID) - etxs, err := bulletprooftxmanager.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringResubmissionDueToInsufficientEth(context.Background(), q, logger.TestLogger(t), fromAddress, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 1) @@ -1004,7 +1220,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) q := pg.NewQ(db, logger.TestLogger(t), cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -1026,7 +1242,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { lggr := logger.TestLogger(t) t.Run("returns nothing when there are no transactions", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -1036,7 +1252,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { nonce++ t.Run("returns nothing when the transaction is in_progress", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -1047,7 +1263,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { nonce++ t.Run("ignores unconfirmed transactions with nil BroadcastBeforeBlockNum", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -1063,7 +1279,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt1_2)) t.Run("returns nothing when the transaction is unconfirmed with an attempt that is recent", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -1075,7 +1291,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { require.NoError(t, db.Get(&attempt2_1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, tooNew, attempt2_1.ID)) t.Run("returns nothing when the transaction has attempts that are too new", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) @@ -1088,19 +1304,19 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { } now := time.Now() etxWithoutAttempts.BroadcastAt = &now - etxWithoutAttempts.State = bulletprooftxmanager.EthTxUnconfirmed + etxWithoutAttempts.State = txmgr.EthTxUnconfirmed require.NoError(t, borm.InsertEthTx(&etxWithoutAttempts)) nonce++ t.Run("does nothing if the transaction is from a different address than the one given", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, otherAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, otherAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, etxs, 0) }) t.Run("returns the transaction if it is unconfirmed and has no attempts (note that this is an invariant violation, but we handle it anyway)", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 1) @@ -1108,7 +1324,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { }) t.Run("returns nothing for different chain id", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, *big.NewInt(42)) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, *big.NewInt(42)) require.NoError(t, err) require.Len(t, etxs, 0) @@ -1125,7 +1341,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { require.NoError(t, db.Get(&attemptOther1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attemptOther1.ID)) t.Run("returns the transaction if it is unconfirmed with an attempt that is older than gasBumpThreshold blocks", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 2) @@ -1134,7 +1350,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { }) t.Run("returns nothing if threshold is zero", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, 0, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, 0, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 0) @@ -1148,13 +1364,13 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { // etxWithoutAttempts (nonce 5) // etx3 (nonce 6) - ready for bump // etx4 (nonce 7) - ready for bump - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 4, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 4, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 1) // returns etxWithoutAttempts only - eligible for gas bumping because it technically doesn't have any attempts withing gasBumpThreshold blocks assert.Equal(t, etxWithoutAttempts.ID, etxs[0].ID) - etxs, err = bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 5, 0, cltest.FixtureChainID) + etxs, err = txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 5, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 2) // includes etxWithoutAttempts, etx3 and etx4 @@ -1162,7 +1378,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { assert.Equal(t, etx3.ID, etxs[1].ID) // Zero limit disables it - etxs, err = bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 0, 0, cltest.FixtureChainID) + etxs, err = txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 0, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 2) // includes etxWithoutAttempts, etx3 and etx4 @@ -1179,7 +1395,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { aOther := etxOther.EthTxAttempts[0] require.NoError(t, db.Get(&aOther, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, aOther.ID)) - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 6, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 6, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 3) // includes etxWithoutAttempts, etx3 and etx4 @@ -1194,7 +1410,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt3_2)) t.Run("returns the transaction if it is unconfirmed with two attempts that are older than gasBumpThreshold blocks", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 3) @@ -1209,7 +1425,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt3_3)) t.Run("does not return the transaction if it has some older but one newer attempt", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 2) @@ -1222,14 +1438,14 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { }) attempt0_1 := newBroadcastLegacyEthTxAttempt(t, etxWithoutAttempts.ID) - attempt0_1.State = bulletprooftxmanager.EthTxAttemptInsufficientEth + attempt0_1.State = txmgr.EthTxAttemptInsufficientEth require.NoError(t, borm.InsertEthTxAttempt(&attempt0_1)) // This attempt has insufficient_eth, but there is also another attempt4_1 // which is old enough, so this will be caught by both queries and should // not be duplicated attempt4_2 := cltest.NewLegacyEthTxAttempt(t, etx4.ID) - attempt4_2.State = bulletprooftxmanager.EthTxAttemptInsufficientEth + attempt4_2.State = txmgr.EthTxAttemptInsufficientEth attempt4_2.GasPrice = utils.NewBigI(40000) require.NoError(t, borm.InsertEthTxAttempt(&attempt4_2)) @@ -1247,7 +1463,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { nonce++ t.Run("returns unique attempts requiring resubmission due to insufficient eth, ordered by nonce asc", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 0, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 4) @@ -1262,7 +1478,7 @@ func TestEthConfirmer_FindEthTxsRequiringRebroadcast(t *testing.T) { }) t.Run("applies limit", func(t *testing.T) { - etxs, err := bulletprooftxmanager.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 2, cltest.FixtureChainID) + etxs, err := txmgr.FindEthTxsRequiringRebroadcast(context.Background(), q, lggr, fromAddress, currentHead, gasBumpThreshold, 10, 2, cltest.FixtureChainID) require.NoError(t, err) require.Len(t, etxs, 2) @@ -1278,7 +1494,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethClient := cltest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -1299,7 +1515,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { nonce := int64(0) t.Run("does nothing if no transactions require bumping", func(t *testing.T) { - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) }) originalBroadcastAt := time.Unix(1616509100, 0) @@ -1318,13 +1534,13 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(nil, errors.New("signing error")).Once() // Do the thing - err = ec.RebroadcastWhereNecessary(context.TODO(), currentHead) + err = ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead) require.Error(t, err) require.Contains(t, err.Error(), "signing error") etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + require.Equal(t, txmgr.EthTxUnconfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 1) @@ -1350,7 +1566,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("exceeds block gas limit")).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -1362,7 +1578,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { }) ethClient = cltest.NewEthClientMockWithDefaultChain(t) - bulletprooftxmanager.SetEthClientOnEthConfirmer(ethClient, ec) + txmgr.SetEthClientOnEthConfirmer(ethClient, ec) t.Run("does nothing and continues if bumped attempt transaction was too expensive", func(t *testing.T) { ethTx := *types.NewTx(&types.LegacyTx{}) @@ -1385,7 +1601,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("tx fee (1.10 ether) exceeds the configured cap (1.00 ether)")).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -1400,9 +1616,9 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { ethClient.AssertExpectations(t) }) - var attempt1_2 bulletprooftxmanager.EthTxAttempt + var attempt1_2 txmgr.EthTxAttempt ethClient = cltest.NewEthClientMockWithDefaultChain(t) - bulletprooftxmanager.SetEthClientOnEthConfirmer(ethClient, ec) + txmgr.SetEthClientOnEthConfirmer(ethClient, ec) t.Run("creates new attempt with higher gas price if transaction has an attempt older than threshold", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(20000000000) @@ -1426,7 +1642,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(nil).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -1437,14 +1653,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { // Got the new attempt attempt1_2 = etx.EthTxAttempts[0] assert.Equal(t, expectedBumpedGasPrice.Int64(), attempt1_2.GasPrice.ToInt().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt1_2.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt1_2.State) ethClient.AssertExpectations(t) }) t.Run("does nothing if there is an attempt without BroadcastBeforeBlockNum set", func(t *testing.T) { // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -1453,7 +1669,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { }) require.NoError(t, db.Get(&attempt1_2, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt1_2.ID)) - var attempt1_3 bulletprooftxmanager.EthTxAttempt + var attempt1_3 txmgr.EthTxAttempt t.Run("creates new attempt with higher gas price if transaction is already in mempool (e.g. due to previous crash before we could save the new attempt)", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(25000000000) @@ -1475,7 +1691,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -1487,14 +1703,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { // Got the new attempt attempt1_3 = etx.EthTxAttempts[0] assert.Equal(t, expectedBumpedGasPrice.Int64(), attempt1_3.GasPrice.ToInt().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt1_3.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt1_3.State) kst.AssertExpectations(t) ethClient.AssertExpectations(t) }) require.NoError(t, db.Get(&attempt1_3, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt1_3.ID)) - var attempt1_4 bulletprooftxmanager.EthTxAttempt + var attempt1_4 txmgr.EthTxAttempt t.Run("saves new attempt even for transaction that has already been confirmed (nonce already used)", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(30000000000) @@ -1518,12 +1734,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("nonce too low")).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx.State) + assert.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx.State) // Got the new attempt attempt1_4 = etx.EthTxAttempts[0] @@ -1534,10 +1750,10 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { require.Equal(t, attempt1_2.ID, etx.EthTxAttempts[2].ID) require.Equal(t, attempt1_3.ID, etx.EthTxAttempts[1].ID) require.Equal(t, attempt1_4.ID, etx.EthTxAttempts[0].ID) - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx.EthTxAttempts[0].State) - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx.EthTxAttempts[1].State) - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx.EthTxAttempts[2].State) - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx.EthTxAttempts[3].State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, etx.EthTxAttempts[0].State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, etx.EthTxAttempts[1].State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, etx.EthTxAttempts[2].State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, etx.EthTxAttempts[3].State) ethClient.AssertExpectations(t) kst.AssertExpectations(t) @@ -1550,7 +1766,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { nonce++ attempt2_1 := etx2.EthTxAttempts[0] require.NoError(t, db.Get(&attempt2_1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt2_1.ID)) - var attempt2_2 bulletprooftxmanager.EthTxAttempt + var attempt2_2 txmgr.EthTxAttempt t.Run("saves in_progress attempt on temporary error and returns error", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(20000000000) @@ -1573,25 +1789,25 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("some network error")).Once() // Do the thing - err = ec.RebroadcastWhereNecessary(context.TODO(), currentHead) + err = ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead) require.Error(t, err) require.Contains(t, err.Error(), "some network error") etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx2.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx2.State) // Old attempt is untouched require.Len(t, etx2.EthTxAttempts, 2) require.Equal(t, attempt2_1.ID, etx2.EthTxAttempts[1].ID) attempt2_1 = etx2.EthTxAttempts[1] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt2_1.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt2_1.State) assert.Equal(t, oldEnough, *attempt2_1.BroadcastBeforeBlockNum) // New in_progress attempt saved attempt2_2 = etx2.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInProgress, attempt2_2.State) + assert.Equal(t, txmgr.EthTxAttemptInProgress, attempt2_2.State) assert.Nil(t, attempt2_2.BroadcastBeforeBlockNum) // Do it again and move the attempt into "broadcast" @@ -1600,19 +1816,19 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { return int64(tx.Nonce()) == n && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 })).Return(nil).Once() - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) // Attempt marked "broadcast" etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx2.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx2.State) // New in_progress attempt saved require.Len(t, etx2.EthTxAttempts, 2) require.Equal(t, attempt2_2.ID, etx2.EthTxAttempts[0].ID) attempt2_2 = etx2.EthTxAttempts[0] - require.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt2_2.State) + require.Equal(t, txmgr.EthTxAttemptBroadcast, attempt2_2.State) assert.Nil(t, attempt2_2.BroadcastBeforeBlockNum) ethClient.AssertExpectations(t) @@ -1643,16 +1859,16 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("nonce too low")).Once() // Creates new attempt as normal if currentHead is not high enough - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx2, err = borm.FindEthTxWithAttempts(etx2.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmedMissingReceipt, etx2.State) + assert.Equal(t, txmgr.EthTxConfirmedMissingReceipt, etx2.State) // One new attempt saved require.Len(t, etx2.EthTxAttempts, 3) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx2.EthTxAttempts[0].State) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx2.EthTxAttempts[1].State) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, etx2.EthTxAttempts[2].State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, etx2.EthTxAttempts[0].State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, etx2.EthTxAttempts[1].State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, etx2.EthTxAttempts[2].State) ethClient.AssertExpectations(t) kst.AssertExpectations(t) @@ -1664,7 +1880,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { attempt3_1 := etx3.EthTxAttempts[0] require.NoError(t, db.Get(&attempt3_1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1, gas_price=$2 WHERE id=$3 RETURNING *`, oldEnough, utils.NewBig(big.NewInt(35000000000)), attempt3_1.ID)) - var attempt3_2 bulletprooftxmanager.EthTxAttempt + var attempt3_2 txmgr.EthTxAttempt t.Run("saves attempt anyway if replacement transaction is underpriced because the bumped gas price is insufficiently higher than the previous one", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(42000000000) @@ -1686,12 +1902,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("replacement transaction underpriced")).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx3, err = borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx3.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx3.State) require.Len(t, etx3.EthTxAttempts, 2) require.Equal(t, attempt3_1.ID, etx3.EthTxAttempts[1].ID) @@ -1704,7 +1920,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { }) require.NoError(t, db.Get(&attempt3_2, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt3_2.ID)) - var attempt3_3 bulletprooftxmanager.EthTxAttempt + var attempt3_3 txmgr.EthTxAttempt t.Run("handles case where transaction is already known somehow", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(50400000000) @@ -1726,12 +1942,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx3, err = borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx3.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx3.State) require.Len(t, etx3.EthTxAttempts, 3) attempt3_3 = etx3.EthTxAttempts[0] @@ -1742,7 +1958,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { }) require.NoError(t, db.Get(&attempt3_3, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt3_3.ID)) - var attempt3_4 bulletprooftxmanager.EthTxAttempt + var attempt3_4 txmgr.EthTxAttempt t.Run("pretends it was accepted and continues the cycle if rejected for being temporarily underpriced", func(t *testing.T) { // This happens if parity is rejecting transactions that are not priced high enough to even get into the mempool at all @@ -1768,12 +1984,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New(temporarilyUnderpricedError)).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx3, err = borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx3.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx3.State) require.Len(t, etx3.EthTxAttempts, 4) attempt3_4 = etx3.EthTxAttempts[0] @@ -1796,12 +2012,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("already known")).Once() // we already submitted at this price, now its time to bump and submit again but since we simply resubmitted rather than increasing gas price, geth already knows about this tx // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx3, err = borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx3.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx3.State) // No new tx attempts require.Len(t, etx3.EthTxAttempts, 4) @@ -1825,12 +2041,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("already known")).Once() // we already submitted at this price, now its time to bump and submit again but since we simply resubmitted rather than increasing gas price, geth already knows about this tx // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx3, err = borm.FindEthTxWithAttempts(etx3.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx3.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx3.State) // No new tx attempts require.Len(t, etx3.EthTxAttempts, 4) @@ -1847,7 +2063,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { attempt4_1 := etx4.EthTxAttempts[0] require.NoError(t, db.Get(&attempt4_1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1, gas_tip_cap=$2, gas_fee_cap=$3 WHERE id=$4 RETURNING *`, oldEnough, utils.NewBig(assets.GWei(35)), utils.NewBig(assets.GWei(100)), attempt4_1.ID)) - var attempt4_2 bulletprooftxmanager.EthTxAttempt + var attempt4_2 txmgr.EthTxAttempt t.Run("EIP-1559: bumps using EIP-1559 rules when existing attempts are of type 0x2", func(t *testing.T) { cfg.Overrides.GlobalEvmMaxGasPriceWei = assets.GWei(1000) @@ -1868,12 +2084,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { return int64(tx.Nonce()) == *etx4.Nonce && gasTipCap.Cmp(tx.GasTipCap()) == 0 })).Return(nil).Once() - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx4, err = borm.FindEthTxWithAttempts(etx4.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx4.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx4.State) // A new, bumped attempt require.Len(t, etx4.EthTxAttempts, 2) @@ -1881,7 +2097,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { assert.Nil(t, attempt4_2.GasPrice) assert.Equal(t, assets.GWei(42).String(), attempt4_2.GasTipCap.String()) assert.Equal(t, assets.GWei(120).String(), attempt4_2.GasFeeCap.String()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt1_2.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt1_2.State) ethClient.AssertExpectations(t) kst.AssertExpectations(t) @@ -1898,12 +2114,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { return int64(tx.Nonce()) == *etx4.Nonce && attempt4_2.Hash == tx.Hash() })).Return(nil).Once() - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx4, err = borm.FindEthTxWithAttempts(etx4.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx4.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx4.State) // No new tx attempts require.Len(t, etx4.EthTxAttempts, 2) @@ -1939,12 +2155,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(errors.New("replacement transaction underpriced")).Once() // Do it - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx4, err = borm.FindEthTxWithAttempts(etx4.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx4.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx4.State) require.Len(t, etx4.EthTxAttempts, 3) require.Equal(t, attempt4_1.ID, etx4.EthTxAttempts[2].ID) @@ -1967,7 +2183,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethClient := cltest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -1987,7 +2203,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh nonce := int64(0) originalBroadcastAt := time.Unix(1616509100, 0) - etx := cltest.MustInsertUnconfrimedEthTxWithAttemptState(t, borm, nonce, fromAddress, bulletprooftxmanager.EthTxAttemptInProgress, originalBroadcastAt) + etx := cltest.MustInsertUnconfrimedEthTxWithAttemptState(t, borm, nonce, fromAddress, txmgr.EthTxAttemptInProgress, originalBroadcastAt) require.Equal(t, originalBroadcastAt, *etx.BroadcastAt) nonce++ attempt := etx.EthTxAttempts[0] @@ -2004,7 +2220,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh kst.On("SignTx", mock.Anything, mock.Anything, mock.Anything).Return( signedTx, nil, ) - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) }) } @@ -2013,7 +2229,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethClient := cltest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -2034,7 +2250,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { nonce++ attempt1_1 := etx.EthTxAttempts[0] require.NoError(t, db.Get(&attempt1_1, `UPDATE eth_tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt1_1.ID)) - var attempt1_2 bulletprooftxmanager.EthTxAttempt + var attempt1_2 txmgr.EthTxAttempt insufficientEthError := errors.New("insufficient funds for gas * price + value") @@ -2049,7 +2265,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { })).Return(insufficientEthError).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -2060,7 +2276,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { // Got the new attempt attempt1_2 = etx.EthTxAttempts[0] assert.Equal(t, expectedBumpedGasPrice.Int64(), attempt1_2.GasPrice.ToInt().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInsufficientEth, attempt1_2.State) + assert.Equal(t, txmgr.EthTxAttemptInsufficientEth, attempt1_2.State) assert.Nil(t, attempt1_2.BroadcastBeforeBlockNum) ethClient.AssertExpectations(t) @@ -2077,7 +2293,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { })).Return(insufficientEthError).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -2088,7 +2304,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { // The attempt is still "out of eth" attempt1_2 = etx.EthTxAttempts[0] assert.Equal(t, expectedBumpedGasPrice.Int64(), attempt1_2.GasPrice.ToInt().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptInsufficientEth, attempt1_2.State) + assert.Equal(t, txmgr.EthTxAttemptInsufficientEth, attempt1_2.State) ethClient.AssertExpectations(t) }) @@ -2104,7 +2320,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { })).Return(nil).Once() // Do the thing - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) etx, err = borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) @@ -2115,7 +2331,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { // Attempt is now 'broadcast' attempt1_2 = etx.EthTxAttempts[0] assert.Equal(t, expectedBumpedGasPrice.Int64(), attempt1_2.GasPrice.ToInt().Int64()) - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt1_2.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt1_2.State) ethClient.AssertExpectations(t) }) @@ -2139,9 +2355,9 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { nonce++ } - require.NoError(t, ec.RebroadcastWhereNecessary(context.TODO(), currentHead)) + require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) - var attempts []bulletprooftxmanager.EthTxAttempt + var attempts []txmgr.EthTxAttempt require.NoError(t, db.Select(&attempts, "SELECT * FROM eth_tx_attempts WHERE state = 'insufficient_eth'")) require.Len(t, attempts, 0) @@ -2154,7 +2370,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -2180,18 +2396,18 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { } t.Run("does nothing if there aren't any transactions", func(t *testing.T) { - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) }) t.Run("does nothing to unconfirmed transactions", func(t *testing.T) { etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 0, fromAddress) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) }) t.Run("does nothing to confirmed transactions with receipts within head height of the chain and included in the chain", func(t *testing.T) { @@ -2200,11 +2416,11 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { cltest.MustInsertEthReceipt(t, borm, head.Number, head.Hash, attempt.Hash) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + assert.Equal(t, txmgr.EthTxConfirmed, etx.State) }) t.Run("does nothing to confirmed transactions that only have receipts older than the start of the chain", func(t *testing.T) { @@ -2214,11 +2430,11 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { cltest.MustInsertEthReceipt(t, borm, head.Parent.Parent.Number-1, utils.NewHash(), attempt.Hash) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + assert.Equal(t, txmgr.EthTxConfirmed, etx.State) }) t.Run("unconfirms and rebroadcasts transactions that have receipts within head height of the chain but not included in the chain", func(t *testing.T) { @@ -2235,14 +2451,14 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { })).Return(nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 1) attempt = etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) ethClient.AssertExpectations(t) }) @@ -2258,14 +2474,14 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { ethClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 1) attempt = etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) ethClient.AssertExpectations(t) }) @@ -2295,18 +2511,18 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { })).Return(nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxUnconfirmed, etx.State) + assert.Equal(t, txmgr.EthTxUnconfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 3) attempt1 := etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt1.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt1.State) attempt2 = etx.EthTxAttempts[1] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt2.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt2.State) attempt3 = etx.EthTxAttempts[2] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt3.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt3.State) ethClient.AssertExpectations(t) }) @@ -2317,14 +2533,14 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { // Add receipt that is higher than head cltest.MustInsertEthReceipt(t, borm, head.Number+1, utils.NewHash(), attempt.Hash) - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(context.TODO(), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) - assert.Equal(t, bulletprooftxmanager.EthTxConfirmed, etx.State) + assert.Equal(t, txmgr.EthTxConfirmed, etx.State) require.Len(t, etx.EthTxAttempts, 1) attempt = etx.EthTxAttempts[0] - assert.Equal(t, bulletprooftxmanager.EthTxAttemptBroadcast, attempt.State) + assert.Equal(t, txmgr.EthTxAttemptBroadcast, attempt.State) assert.Len(t, attempt.EthReceipts, 1) ethClient.AssertExpectations(t) @@ -2336,7 +2552,7 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() state, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore, 0) @@ -2446,7 +2662,7 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { db := pgtest.NewSqlxDB(t) config := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, config) + borm := cltest.NewTxmORM(t, db, config) ethKeyStore := cltest.NewKeyStore(t, db, config).Eth() diff --git a/core/chains/evm/bulletprooftxmanager/eth_resender.go b/core/chains/evm/txmgr/eth_resender.go similarity index 66% rename from core/chains/evm/bulletprooftxmanager/eth_resender.go rename to core/chains/evm/txmgr/eth_resender.go index 3565bc5bb5b..0c13eec2a23 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_resender.go +++ b/core/chains/evm/txmgr/eth_resender.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "context" @@ -6,10 +6,7 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - "github.com/lib/pq" "github.com/pkg/errors" "github.com/smartcontractkit/sqlx" @@ -105,59 +102,30 @@ func (er *EthResender) resendUnconfirmed() error { maxInFlightTransactions := er.config.EvmMaxInFlightTransactions() olderThan := time.Now().Add(-ageThreshold) - attempts, err := FindEthTxesRequiringResend(er.db, olderThan, maxInFlightTransactions, er.chainID) + attempts, err := FindEthTxAttemptsRequiringResend(er.db, olderThan, maxInFlightTransactions, er.chainID) if err != nil { - return errors.Wrap(err, "failed to FindEthTxesRequiringResend") + return errors.Wrap(err, "failed to FindEthTxAttemptsRequiringResend") } if len(attempts) == 0 { return nil } - reqs := make([]rpc.BatchElem, len(attempts)) - ethTxIDs := make([]int64, len(attempts)) - for i, attempt := range attempts { - ethTxIDs[i] = attempt.EthTxID - req := rpc.BatchElem{ - Method: "eth_sendRawTransaction", - Args: []interface{}{hexutil.Encode(attempt.SignedRawTx)}, - Result: &common.Hash{}, - } - reqs[i] = req - } + er.logger.Infow(fmt.Sprintf("Re-sending %d unconfirmed transactions that were last sent over %s ago. These transactions are taking longer than usual to be mined. %s", len(attempts), ageThreshold, static.EthNodeConnectivityProblemLabel), "n", len(attempts)) - er.logger.Infow(fmt.Sprintf("Re-sending %d unconfirmed transactions that were last sent over %s ago. These transactions are taking longer than usual to be mined. %s", len(attempts), ageThreshold, static.EthNodeConnectivityProblemLabel), "n", len(attempts), "ethTxIDs", ethTxIDs) - - now := time.Now() batchSize := int(er.config.EvmRPCDefaultBatchSize()) - if batchSize == 0 { - batchSize = len(reqs) - } - for i := 0; i < len(reqs); i += batchSize { - j := i + batchSize - if j > len(reqs) { - j = len(reqs) - } - - er.logger.Debugw(fmt.Sprintf("Batch resending transactions %v thru %v", i, j)) - - if err := er.ethClient.BatchCallContext(er.ctx, reqs[i:j]); err != nil { - return errors.Wrap(err, "failed to re-send transactions") - } - - if err := er.updateBroadcastAts(now, ethTxIDs[i:j]); err != nil { - return errors.Wrap(err, "failed to update last succeeded on attempts") - } + reqs, err := batchSendTransactions(er.ctx, er.db, attempts, batchSize, er.logger, er.ethClient) + if err != nil { + return errors.Wrap(err, "failed to re-send transactions") } - logResendResult(er.logger, reqs) return nil } -// FindEthTxesRequiringResend returns the highest priced attempt for each +// FindEthTxAttemptsRequiringResend returns the highest priced attempt for each // eth_tx that was last sent before or at the given time (up to limit) -func FindEthTxesRequiringResend(db *sqlx.DB, olderThan time.Time, maxInFlightTransactions uint32, chainID big.Int) (attempts []EthTxAttempt, err error) { +func FindEthTxAttemptsRequiringResend(db *sqlx.DB, olderThan time.Time, maxInFlightTransactions uint32, chainID big.Int) (attempts []EthTxAttempt, err error) { var limit null.Uint32 if maxInFlightTransactions > 0 { limit = null.Uint32From(maxInFlightTransactions) @@ -167,23 +135,11 @@ SELECT DISTINCT ON (eth_tx_id) eth_tx_attempts.* FROM eth_tx_attempts JOIN eth_txes ON eth_txes.id = eth_tx_attempts.eth_tx_id AND eth_txes.state IN ('unconfirmed', 'confirmed_missing_receipt') WHERE eth_tx_attempts.state <> 'in_progress' AND eth_txes.broadcast_at <= $1 AND evm_chain_id = $2 -ORDER BY eth_tx_attempts.eth_tx_id ASC, eth_txes.nonce ASC, eth_tx_attempts.gas_price DESC +ORDER BY eth_tx_attempts.eth_tx_id ASC, eth_txes.nonce ASC, eth_tx_attempts.gas_price DESC, eth_tx_attempts.gas_tip_cap DESC LIMIT $3 `, olderThan, chainID.String(), limit) - return attempts, errors.Wrap(err, "FindEthTxesRequiringResend failed to load eth_tx_attempts") -} - -func (er *EthResender) updateBroadcastAts(now time.Time, etxIDs []int64) error { - // Deliberately do nothing on NULL broadcast_at because that indicates the - // tx has been moved into a state where broadcast_at is not relevant, e.g. - // fatally errored. - // - // Since we may have raced with the EthConfirmer (totally OK since highest - // priced transaction always wins) we only want to update broadcast_at if - // our version is later. - _, err := er.db.Exec(`UPDATE eth_txes SET broadcast_at = $1 WHERE id = ANY($2) AND broadcast_at < $1`, now, pq.Array(etxIDs)) - return errors.Wrap(err, "updateBroadcastAts failed to update eth_txes") + return attempts, errors.Wrap(err, "FindEthTxAttemptsRequiringResend failed to load eth_tx_attempts") } func logResendResult(lggr logger.Logger, reqs []rpc.BatchElem) { diff --git a/core/chains/evm/bulletprooftxmanager/eth_resender_test.go b/core/chains/evm/txmgr/eth_resender_test.go similarity index 67% rename from core/chains/evm/bulletprooftxmanager/eth_resender_test.go rename to core/chains/evm/txmgr/eth_resender_test.go index 47f85b9bcaf..e50e266fa9f 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_resender_test.go +++ b/core/chains/evm/txmgr/eth_resender_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "math/big" @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" @@ -21,12 +21,12 @@ import ( "gopkg.in/guregu/null.v4" ) -func Test_EthResender_FindEthTxesRequiringResend(t *testing.T) { +func Test_EthResender_FindEthTxAttemptsRequiringResend(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() @@ -34,15 +34,16 @@ func Test_EthResender_FindEthTxesRequiringResend(t *testing.T) { t.Run("returns nothing if there are no transactions", func(t *testing.T) { olderThan := time.Now() - attempts, err := bulletprooftxmanager.FindEthTxesRequiringResend(db, olderThan, 10, cltest.FixtureChainID) + attempts, err := txmgr.FindEthTxAttemptsRequiringResend(db, olderThan, 10, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, attempts, 0) }) - etxs := []bulletprooftxmanager.EthTx{ + etxs := []txmgr.EthTx{ cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 0, fromAddress, time.Unix(1616509100, 0)), cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 1, fromAddress, time.Unix(1616509200, 0)), cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 2, fromAddress, time.Unix(1616509300, 0)), + cltest.MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, borm, 3, fromAddress, time.Unix(1616509400, 0)), } attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etxs[0].ID) attempt1_2.GasPrice = utils.NewBig(big.NewInt(10)) @@ -52,18 +53,42 @@ func Test_EthResender_FindEthTxesRequiringResend(t *testing.T) { attempt3_2.GasPrice = utils.NewBig(big.NewInt(10)) require.NoError(t, borm.InsertEthTxAttempt(&attempt3_2)) + attempt4_2 := cltest.NewDynamicFeeEthTxAttempt(t, etxs[3].ID) + attempt4_2.GasTipCap = utils.NewBig(big.NewInt(10)) + attempt4_2.GasFeeCap = utils.NewBig(big.NewInt(20)) + attempt4_2.State = txmgr.EthTxAttemptBroadcast + require.NoError(t, borm.InsertEthTxAttempt(&attempt4_2)) + attempt4_4 := cltest.NewDynamicFeeEthTxAttempt(t, etxs[3].ID) + attempt4_4.GasTipCap = utils.NewBig(big.NewInt(30)) + attempt4_4.GasFeeCap = utils.NewBig(big.NewInt(40)) + attempt4_4.State = txmgr.EthTxAttemptBroadcast + require.NoError(t, borm.InsertEthTxAttempt(&attempt4_4)) + attempt4_3 := cltest.NewDynamicFeeEthTxAttempt(t, etxs[3].ID) + attempt4_3.GasTipCap = utils.NewBig(big.NewInt(20)) + attempt4_3.GasFeeCap = utils.NewBig(big.NewInt(30)) + attempt4_3.State = txmgr.EthTxAttemptBroadcast + require.NoError(t, borm.InsertEthTxAttempt(&attempt4_3)) + t.Run("returns the highest price attempt for each transaction that was last broadcast before or on the given time", func(t *testing.T) { olderThan := time.Unix(1616509200, 0) - attempts, err := bulletprooftxmanager.FindEthTxesRequiringResend(db, olderThan, 0, cltest.FixtureChainID) + attempts, err := txmgr.FindEthTxAttemptsRequiringResend(db, olderThan, 0, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, attempts, 2) assert.Equal(t, attempt1_2.ID, attempts[0].ID) assert.Equal(t, etxs[1].EthTxAttempts[0].ID, attempts[1].ID) }) + t.Run("returns the highest price attempt for EIP-1559 transactions", func(t *testing.T) { + olderThan := time.Unix(1616509400, 0) + attempts, err := txmgr.FindEthTxAttemptsRequiringResend(db, olderThan, 0, cltest.FixtureChainID) + require.NoError(t, err) + assert.Len(t, attempts, 4) + assert.Equal(t, attempt4_4.ID, attempts[3].ID) + }) + t.Run("applies limit", func(t *testing.T) { olderThan := time.Unix(1616509200, 0) - attempts, err := bulletprooftxmanager.FindEthTxesRequiringResend(db, olderThan, 1, cltest.FixtureChainID) + attempts, err := txmgr.FindEthTxAttemptsRequiringResend(db, olderThan, 1, cltest.FixtureChainID) require.NoError(t, err) assert.Len(t, attempts, 1) assert.Equal(t, attempt1_2.ID, attempts[0].ID) @@ -75,7 +100,7 @@ func Test_EthResender_Start(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() // This can be anything as long as it isn't zero d := 42 * time.Hour @@ -89,7 +114,7 @@ func Test_EthResender_Start(t *testing.T) { t.Run("resends transactions that have been languishing unconfirmed for too long", func(t *testing.T) { ethClient := cltest.NewEthClientMockWithDefaultChain(t) - er := bulletprooftxmanager.NewEthResender(lggr, db, ethClient, 100*time.Millisecond, evmcfg) + er := txmgr.NewEthResender(lggr, db, ethClient, 100*time.Millisecond, evmcfg) originalBroadcastAt := time.Unix(1616509100, 0) etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 0, fromAddress, originalBroadcastAt) @@ -97,12 +122,12 @@ func Test_EthResender_Start(t *testing.T) { cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 2, fromAddress, time.Now().Add(1*time.Hour)) // First batch of 1 - ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 1 && b[0].Method == "eth_sendRawTransaction" && b[0].Args[0] == hexutil.Encode(etx.EthTxAttempts[0].SignedRawTx) })).Return(nil) // Second batch of 1 - ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 1 && b[0].Method == "eth_sendRawTransaction" && b[0].Args[0] == hexutil.Encode(etx2.EthTxAttempts[0].SignedRawTx) })).Return(nil).Run(func(args mock.Arguments) { diff --git a/core/chains/evm/bulletprooftxmanager/helpers_test.go b/core/chains/evm/txmgr/helpers_test.go similarity index 92% rename from core/chains/evm/bulletprooftxmanager/helpers_test.go rename to core/chains/evm/txmgr/helpers_test.go index 5cad69f2b07..847614790dc 100644 --- a/core/chains/evm/bulletprooftxmanager/helpers_test.go +++ b/core/chains/evm/txmgr/helpers_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" diff --git a/core/chains/evm/bulletprooftxmanager/mocks/config.go b/core/chains/evm/txmgr/mocks/config.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/mocks/config.go rename to core/chains/evm/txmgr/mocks/config.go index 2f70d033978..efc0b3674c3 100644 --- a/core/chains/evm/bulletprooftxmanager/mocks/config.go +++ b/core/chains/evm/txmgr/mocks/config.go @@ -5,9 +5,8 @@ package mocks import ( big "math/big" - chains "github.com/smartcontractkit/chainlink/core/chains" - common "github.com/ethereum/go-ethereum/common" + chains "github.com/smartcontractkit/chainlink/core/chains" mock "github.com/stretchr/testify/mock" diff --git a/core/chains/evm/bulletprooftxmanager/mocks/orm.go b/core/chains/evm/txmgr/mocks/orm.go similarity index 58% rename from core/chains/evm/bulletprooftxmanager/mocks/orm.go rename to core/chains/evm/txmgr/mocks/orm.go index 37765f7acfa..4fd09b33c76 100644 --- a/core/chains/evm/bulletprooftxmanager/mocks/orm.go +++ b/core/chains/evm/txmgr/mocks/orm.go @@ -4,9 +4,9 @@ package mocks import ( common "github.com/ethereum/go-ethereum/common" - bulletprooftxmanager "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - mock "github.com/stretchr/testify/mock" + + txmgr "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" ) // ORM is an autogenerated mock type for the ORM type @@ -15,15 +15,15 @@ type ORM struct { } // EthTransactions provides a mock function with given fields: offset, limit -func (_m *ORM) EthTransactions(offset int, limit int) ([]bulletprooftxmanager.EthTx, int, error) { +func (_m *ORM) EthTransactions(offset int, limit int) ([]txmgr.EthTx, int, error) { ret := _m.Called(offset, limit) - var r0 []bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(int, int) []bulletprooftxmanager.EthTx); ok { + var r0 []txmgr.EthTx + if rf, ok := ret.Get(0).(func(int, int) []txmgr.EthTx); ok { r0 = rf(offset, limit) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]bulletprooftxmanager.EthTx) + r0 = ret.Get(0).([]txmgr.EthTx) } } @@ -45,15 +45,15 @@ func (_m *ORM) EthTransactions(offset int, limit int) ([]bulletprooftxmanager.Et } // EthTransactionsWithAttempts provides a mock function with given fields: offset, limit -func (_m *ORM) EthTransactionsWithAttempts(offset int, limit int) ([]bulletprooftxmanager.EthTx, int, error) { +func (_m *ORM) EthTransactionsWithAttempts(offset int, limit int) ([]txmgr.EthTx, int, error) { ret := _m.Called(offset, limit) - var r0 []bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(int, int) []bulletprooftxmanager.EthTx); ok { + var r0 []txmgr.EthTx + if rf, ok := ret.Get(0).(func(int, int) []txmgr.EthTx); ok { r0 = rf(offset, limit) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]bulletprooftxmanager.EthTx) + r0 = ret.Get(0).([]txmgr.EthTx) } } @@ -75,15 +75,15 @@ func (_m *ORM) EthTransactionsWithAttempts(offset int, limit int) ([]bulletproof } // EthTxAttempts provides a mock function with given fields: offset, limit -func (_m *ORM) EthTxAttempts(offset int, limit int) ([]bulletprooftxmanager.EthTxAttempt, int, error) { +func (_m *ORM) EthTxAttempts(offset int, limit int) ([]txmgr.EthTxAttempt, int, error) { ret := _m.Called(offset, limit) - var r0 []bulletprooftxmanager.EthTxAttempt - if rf, ok := ret.Get(0).(func(int, int) []bulletprooftxmanager.EthTxAttempt); ok { + var r0 []txmgr.EthTxAttempt + if rf, ok := ret.Get(0).(func(int, int) []txmgr.EthTxAttempt); ok { r0 = rf(offset, limit) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]bulletprooftxmanager.EthTxAttempt) + r0 = ret.Get(0).([]txmgr.EthTxAttempt) } } @@ -105,15 +105,15 @@ func (_m *ORM) EthTxAttempts(offset int, limit int) ([]bulletprooftxmanager.EthT } // FindEthTxAttempt provides a mock function with given fields: hash -func (_m *ORM) FindEthTxAttempt(hash common.Hash) (*bulletprooftxmanager.EthTxAttempt, error) { +func (_m *ORM) FindEthTxAttempt(hash common.Hash) (*txmgr.EthTxAttempt, error) { ret := _m.Called(hash) - var r0 *bulletprooftxmanager.EthTxAttempt - if rf, ok := ret.Get(0).(func(common.Hash) *bulletprooftxmanager.EthTxAttempt); ok { + var r0 *txmgr.EthTxAttempt + if rf, ok := ret.Get(0).(func(common.Hash) *txmgr.EthTxAttempt); ok { r0 = rf(hash) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*bulletprooftxmanager.EthTxAttempt) + r0 = ret.Get(0).(*txmgr.EthTxAttempt) } } @@ -128,15 +128,15 @@ func (_m *ORM) FindEthTxAttempt(hash common.Hash) (*bulletprooftxmanager.EthTxAt } // FindEthTxAttemptsByEthTxIDs provides a mock function with given fields: ids -func (_m *ORM) FindEthTxAttemptsByEthTxIDs(ids []int64) ([]bulletprooftxmanager.EthTxAttempt, error) { +func (_m *ORM) FindEthTxAttemptsByEthTxIDs(ids []int64) ([]txmgr.EthTxAttempt, error) { ret := _m.Called(ids) - var r0 []bulletprooftxmanager.EthTxAttempt - if rf, ok := ret.Get(0).(func([]int64) []bulletprooftxmanager.EthTxAttempt); ok { + var r0 []txmgr.EthTxAttempt + if rf, ok := ret.Get(0).(func([]int64) []txmgr.EthTxAttempt); ok { r0 = rf(ids) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]bulletprooftxmanager.EthTxAttempt) + r0 = ret.Get(0).([]txmgr.EthTxAttempt) } } @@ -151,15 +151,15 @@ func (_m *ORM) FindEthTxAttemptsByEthTxIDs(ids []int64) ([]bulletprooftxmanager. } // FindEthTxByHash provides a mock function with given fields: hash -func (_m *ORM) FindEthTxByHash(hash common.Hash) (*bulletprooftxmanager.EthTx, error) { +func (_m *ORM) FindEthTxByHash(hash common.Hash) (*txmgr.EthTx, error) { ret := _m.Called(hash) - var r0 *bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(common.Hash) *bulletprooftxmanager.EthTx); ok { + var r0 *txmgr.EthTx + if rf, ok := ret.Get(0).(func(common.Hash) *txmgr.EthTx); ok { r0 = rf(hash) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*bulletprooftxmanager.EthTx) + r0 = ret.Get(0).(*txmgr.EthTx) } } @@ -174,14 +174,14 @@ func (_m *ORM) FindEthTxByHash(hash common.Hash) (*bulletprooftxmanager.EthTx, e } // FindEthTxWithAttempts provides a mock function with given fields: etxID -func (_m *ORM) FindEthTxWithAttempts(etxID int64) (bulletprooftxmanager.EthTx, error) { +func (_m *ORM) FindEthTxWithAttempts(etxID int64) (txmgr.EthTx, error) { ret := _m.Called(etxID) - var r0 bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(int64) bulletprooftxmanager.EthTx); ok { + var r0 txmgr.EthTx + if rf, ok := ret.Get(0).(func(int64) txmgr.EthTx); ok { r0 = rf(etxID) } else { - r0 = ret.Get(0).(bulletprooftxmanager.EthTx) + r0 = ret.Get(0).(txmgr.EthTx) } var r1 error @@ -195,11 +195,11 @@ func (_m *ORM) FindEthTxWithAttempts(etxID int64) (bulletprooftxmanager.EthTx, e } // InsertEthReceipt provides a mock function with given fields: receipt -func (_m *ORM) InsertEthReceipt(receipt *bulletprooftxmanager.EthReceipt) error { +func (_m *ORM) InsertEthReceipt(receipt *txmgr.EthReceipt) error { ret := _m.Called(receipt) var r0 error - if rf, ok := ret.Get(0).(func(*bulletprooftxmanager.EthReceipt) error); ok { + if rf, ok := ret.Get(0).(func(*txmgr.EthReceipt) error); ok { r0 = rf(receipt) } else { r0 = ret.Error(0) @@ -209,11 +209,11 @@ func (_m *ORM) InsertEthReceipt(receipt *bulletprooftxmanager.EthReceipt) error } // InsertEthTx provides a mock function with given fields: etx -func (_m *ORM) InsertEthTx(etx *bulletprooftxmanager.EthTx) error { +func (_m *ORM) InsertEthTx(etx *txmgr.EthTx) error { ret := _m.Called(etx) var r0 error - if rf, ok := ret.Get(0).(func(*bulletprooftxmanager.EthTx) error); ok { + if rf, ok := ret.Get(0).(func(*txmgr.EthTx) error); ok { r0 = rf(etx) } else { r0 = ret.Error(0) @@ -223,11 +223,11 @@ func (_m *ORM) InsertEthTx(etx *bulletprooftxmanager.EthTx) error { } // InsertEthTxAttempt provides a mock function with given fields: attempt -func (_m *ORM) InsertEthTxAttempt(attempt *bulletprooftxmanager.EthTxAttempt) error { +func (_m *ORM) InsertEthTxAttempt(attempt *txmgr.EthTxAttempt) error { ret := _m.Called(attempt) var r0 error - if rf, ok := ret.Get(0).(func(*bulletprooftxmanager.EthTxAttempt) error); ok { + if rf, ok := ret.Get(0).(func(*txmgr.EthTxAttempt) error); ok { r0 = rf(attempt) } else { r0 = ret.Error(0) diff --git a/core/chains/evm/bulletprooftxmanager/mocks/reaper_config.go b/core/chains/evm/txmgr/mocks/reaper_config.go similarity index 100% rename from core/chains/evm/bulletprooftxmanager/mocks/reaper_config.go rename to core/chains/evm/txmgr/mocks/reaper_config.go diff --git a/core/chains/evm/bulletprooftxmanager/mocks/tx_manager.go b/core/chains/evm/txmgr/mocks/tx_manager.go similarity index 75% rename from core/chains/evm/bulletprooftxmanager/mocks/tx_manager.go rename to core/chains/evm/txmgr/mocks/tx_manager.go index d63cb7441e8..f6a3a6c403a 100644 --- a/core/chains/evm/bulletprooftxmanager/mocks/tx_manager.go +++ b/core/chains/evm/txmgr/mocks/tx_manager.go @@ -7,8 +7,6 @@ import ( assets "github.com/smartcontractkit/chainlink/core/assets" - bulletprooftxmanager "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - common "github.com/ethereum/go-ethereum/common" context "context" @@ -19,6 +17,8 @@ import ( pg "github.com/smartcontractkit/chainlink/core/services/pg" + txmgr "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + types "github.com/smartcontractkit/chainlink/core/chains/evm/types" ) @@ -42,7 +42,7 @@ func (_m *TxManager) Close() error { } // CreateEthTransaction provides a mock function with given fields: newTx, qopts -func (_m *TxManager) CreateEthTransaction(newTx bulletprooftxmanager.NewTx, qopts ...pg.QOpt) (bulletprooftxmanager.EthTx, error) { +func (_m *TxManager) CreateEthTransaction(newTx txmgr.NewTx, qopts ...pg.QOpt) (txmgr.EthTx, error) { _va := make([]interface{}, len(qopts)) for _i := range qopts { _va[_i] = qopts[_i] @@ -52,15 +52,15 @@ func (_m *TxManager) CreateEthTransaction(newTx bulletprooftxmanager.NewTx, qopt _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(bulletprooftxmanager.NewTx, ...pg.QOpt) bulletprooftxmanager.EthTx); ok { + var r0 txmgr.EthTx + if rf, ok := ret.Get(0).(func(txmgr.NewTx, ...pg.QOpt) txmgr.EthTx); ok { r0 = rf(newTx, qopts...) } else { - r0 = ret.Get(0).(bulletprooftxmanager.EthTx) + r0 = ret.Get(0).(txmgr.EthTx) } var r1 error - if rf, ok := ret.Get(1).(func(bulletprooftxmanager.NewTx, ...pg.QOpt) error); ok { + if rf, ok := ret.Get(1).(func(txmgr.NewTx, ...pg.QOpt) error); ok { r1 = rf(newTx, qopts...) } else { r1 = ret.Error(1) @@ -119,19 +119,19 @@ func (_m *TxManager) Ready() error { } // RegisterResumeCallback provides a mock function with given fields: fn -func (_m *TxManager) RegisterResumeCallback(fn bulletprooftxmanager.ResumeCallback) { +func (_m *TxManager) RegisterResumeCallback(fn txmgr.ResumeCallback) { _m.Called(fn) } // SendEther provides a mock function with given fields: chainID, from, to, value, gasLimit -func (_m *TxManager) SendEther(chainID *big.Int, from common.Address, to common.Address, value assets.Eth, gasLimit uint64) (bulletprooftxmanager.EthTx, error) { +func (_m *TxManager) SendEther(chainID *big.Int, from common.Address, to common.Address, value assets.Eth, gasLimit uint64) (txmgr.EthTx, error) { ret := _m.Called(chainID, from, to, value, gasLimit) - var r0 bulletprooftxmanager.EthTx - if rf, ok := ret.Get(0).(func(*big.Int, common.Address, common.Address, assets.Eth, uint64) bulletprooftxmanager.EthTx); ok { + var r0 txmgr.EthTx + if rf, ok := ret.Get(0).(func(*big.Int, common.Address, common.Address, assets.Eth, uint64) txmgr.EthTx); ok { r0 = rf(chainID, from, to, value, gasLimit) } else { - r0 = ret.Get(0).(bulletprooftxmanager.EthTx) + r0 = ret.Get(0).(txmgr.EthTx) } var r1 error @@ -144,13 +144,13 @@ func (_m *TxManager) SendEther(chainID *big.Int, from common.Address, to common. return r0, r1 } -// Start provides a mock function with given fields: -func (_m *TxManager) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *TxManager) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/evm/bulletprooftxmanager/mocks/tx_strategy.go b/core/chains/evm/txmgr/mocks/tx_strategy.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/mocks/tx_strategy.go rename to core/chains/evm/txmgr/mocks/tx_strategy.go index cb30f2d4182..1fb518c447e 100644 --- a/core/chains/evm/bulletprooftxmanager/mocks/tx_strategy.go +++ b/core/chains/evm/txmgr/mocks/tx_strategy.go @@ -3,9 +3,10 @@ package mocks import ( - uuid "github.com/satori/go.uuid" pg "github.com/smartcontractkit/chainlink/core/services/pg" mock "github.com/stretchr/testify/mock" + + uuid "github.com/satori/go.uuid" ) // TxStrategy is an autogenerated mock type for the TxStrategy type diff --git a/core/chains/evm/bulletprooftxmanager/models.go b/core/chains/evm/txmgr/models.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/models.go rename to core/chains/evm/txmgr/models.go index 1258ef48478..c3fd166688f 100644 --- a/core/chains/evm/bulletprooftxmanager/models.go +++ b/core/chains/evm/txmgr/models.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "bytes" diff --git a/core/chains/evm/bulletprooftxmanager/nonce_syncer.go b/core/chains/evm/txmgr/nonce_syncer.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/nonce_syncer.go rename to core/chains/evm/txmgr/nonce_syncer.go index 70619495c70..8b88ac4627f 100644 --- a/core/chains/evm/bulletprooftxmanager/nonce_syncer.go +++ b/core/chains/evm/txmgr/nonce_syncer.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "context" diff --git a/core/chains/evm/bulletprooftxmanager/nonce_syncer_test.go b/core/chains/evm/txmgr/nonce_syncer_test.go similarity index 89% rename from core/chains/evm/bulletprooftxmanager/nonce_syncer_test.go rename to core/chains/evm/txmgr/nonce_syncer_test.go index 4872a04ff0f..5923850b403 100644 --- a/core/chains/evm/bulletprooftxmanager/nonce_syncer_test.go +++ b/core/chains/evm/txmgr/nonce_syncer_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" @@ -31,7 +31,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { return from == addr })).Return(uint64(0), errors.New("something exploded")) - ns := bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns := txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) sendingKeys := cltest.MustSendingKeyStates(t, ethKeyStore) err := ns.SyncAll(context.Background(), sendingKeys) @@ -58,7 +58,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { return from == addr })).Return(uint64(0), nil) - ns := bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns := txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) sendingKeys := cltest.MustSendingKeyStates(t, ethKeyStore) require.NoError(t, ns.SyncAll(context.Background(), sendingKeys)) @@ -84,7 +84,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { return k1.Address.Address() == addr })).Return(uint64(31), nil) - ns := bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns := txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) sendingKeys := cltest.MustSendingKeyStates(t, ethKeyStore) require.NoError(t, ns.SyncAll(context.Background(), sendingKeys)) @@ -116,7 +116,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { return key1 == addr })).Return(uint64(5), nil) - ns := bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns := txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) sendingKeys := cltest.MustSendingKeyStates(t, ethKeyStore) require.NoError(t, ns.SyncAll(context.Background(), sendingKeys)) @@ -129,7 +129,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { t.Run("counts 'in_progress' eth_tx as bumping the local next nonce by 1", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, key1 := cltest.MustInsertRandomKey(t, ethKeyStore, int64(0)) @@ -142,7 +142,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { // by 1, but does not need to change when taking into account the in_progress tx return key1 == addr })).Return(uint64(1), nil) - ns := bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns := txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) sendingKeys := cltest.MustSendingKeyStates(t, ethKeyStore) require.NoError(t, ns.SyncAll(context.Background(), sendingKeys)) @@ -156,7 +156,7 @@ func Test_NonceSyncer_SyncAll(t *testing.T) { // by 2, but only ahead by 1 if we count the in_progress tx as +1 return key1 == addr })).Return(uint64(2), nil) - ns = bulletprooftxmanager.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) + ns = txmgr.NewNonceSyncer(db, logger.TestLogger(t), cfg, ethClient) require.NoError(t, ns.SyncAll(context.Background(), sendingKeys)) assertDatabaseNonce(t, db, key1, 1) diff --git a/core/chains/evm/bulletprooftxmanager/orm.go b/core/chains/evm/txmgr/orm.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/orm.go rename to core/chains/evm/txmgr/orm.go index eb8bb336e10..d3e5b439ed4 100644 --- a/core/chains/evm/bulletprooftxmanager/orm.go +++ b/core/chains/evm/txmgr/orm.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "time" @@ -35,7 +35,7 @@ type orm struct { var _ ORM = (*orm)(nil) func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig) ORM { - namedLogger := lggr.Named("BulletproofTxManagerORM") + namedLogger := lggr.Named("TxmORM") q := pg.NewQ(db, namedLogger, cfg) return &orm{q, namedLogger} } diff --git a/core/chains/evm/bulletprooftxmanager/orm_test.go b/core/chains/evm/txmgr/orm_test.go similarity index 89% rename from core/chains/evm/bulletprooftxmanager/orm_test.go rename to core/chains/evm/txmgr/orm_test.go index 8cc35a0216b..afffc461c9e 100644 --- a/core/chains/evm/bulletprooftxmanager/orm_test.go +++ b/core/chains/evm/txmgr/orm_test.go @@ -1,10 +1,10 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/utils" @@ -16,7 +16,7 @@ import ( func TestORM_EthTransactionsWithAttempts(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - orm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + orm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, from := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -27,14 +27,14 @@ func TestORM_EthTransactionsWithAttempts(t *testing.T) { // add 2nd attempt to tx2 blockNum := int64(3) attempt := cltest.NewLegacyEthTxAttempt(t, tx2.ID) - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast attempt.GasPrice = utils.NewBig(big.NewInt(3)) attempt.BroadcastBeforeBlockNum = &blockNum require.NoError(t, orm.InsertEthTxAttempt(&attempt)) // tx 3 has no attempts tx3 := cltest.NewEthTx(t, from) - tx3.State = bulletprooftxmanager.EthTxUnstarted + tx3.State = txmgr.EthTxUnstarted tx3.FromAddress = from require.NoError(t, orm.InsertEthTx(&tx3)) @@ -64,7 +64,7 @@ func TestORM_EthTransactionsWithAttempts(t *testing.T) { func TestORM_EthTransactions(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - orm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + orm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, from := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -75,14 +75,14 @@ func TestORM_EthTransactions(t *testing.T) { // add 2nd attempt to tx2 blockNum := int64(3) attempt := cltest.NewLegacyEthTxAttempt(t, tx2.ID) - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast attempt.GasPrice = utils.NewBig(big.NewInt(3)) attempt.BroadcastBeforeBlockNum = &blockNum require.NoError(t, orm.InsertEthTxAttempt(&attempt)) // tx 3 has no attempts tx3 := cltest.NewEthTx(t, from) - tx3.State = bulletprooftxmanager.EthTxUnstarted + tx3.State = txmgr.EthTxUnstarted tx3.FromAddress = from require.NoError(t, orm.InsertEthTx(&tx3)) @@ -107,11 +107,11 @@ func TestORM(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) keyStore := cltest.NewKeyStore(t, db, cfg) - orm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + orm := cltest.NewTxmORM(t, db, cfg) _, fromAddress := cltest.MustInsertRandomKey(t, keyStore.Eth(), 0) var err error - var etx bulletprooftxmanager.EthTx + var etx txmgr.EthTx t.Run("InsertEthTx", func(t *testing.T) { etx = cltest.NewEthTx(t, fromAddress) err = orm.InsertEthTx(&etx) @@ -119,8 +119,8 @@ func TestORM(t *testing.T) { assert.Greater(t, int(etx.ID), 0) cltest.AssertCount(t, db, "eth_txes", 1) }) - var attemptL bulletprooftxmanager.EthTxAttempt - var attemptD bulletprooftxmanager.EthTxAttempt + var attemptL txmgr.EthTxAttempt + var attemptD txmgr.EthTxAttempt t.Run("InsertEthTxAttempt", func(t *testing.T) { attemptD = cltest.NewDynamicFeeEthTxAttempt(t, etx.ID) err = orm.InsertEthTxAttempt(&attemptD) @@ -129,14 +129,14 @@ func TestORM(t *testing.T) { cltest.AssertCount(t, db, "eth_tx_attempts", 1) attemptL = cltest.NewLegacyEthTxAttempt(t, etx.ID) - attemptL.State = bulletprooftxmanager.EthTxAttemptBroadcast + attemptL.State = txmgr.EthTxAttemptBroadcast attemptL.GasPrice = utils.NewBigI(42) err = orm.InsertEthTxAttempt(&attemptL) require.NoError(t, err) assert.Greater(t, int(attemptL.ID), 0) cltest.AssertCount(t, db, "eth_tx_attempts", 2) }) - var r bulletprooftxmanager.EthReceipt + var r txmgr.EthReceipt t.Run("InsertEthReceipt", func(t *testing.T) { r = cltest.NewEthReceipt(t, 42, utils.NewHash(), attemptD.Hash) err = orm.InsertEthReceipt(&r) diff --git a/core/chains/evm/bulletprooftxmanager/reaper.go b/core/chains/evm/txmgr/reaper.go similarity index 77% rename from core/chains/evm/bulletprooftxmanager/reaper.go rename to core/chains/evm/txmgr/reaper.go index 45d997a29fd..582f4e7a03e 100644 --- a/core/chains/evm/bulletprooftxmanager/reaper.go +++ b/core/chains/evm/txmgr/reaper.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "fmt" @@ -22,7 +22,7 @@ type ReaperConfig interface { EvmFinalityDepth() uint32 } -// Reaper handles periodic database cleanup for BPTXM +// Reaper handles periodic database cleanup for Txm type Reaper struct { db *sqlx.DB config ReaperConfig @@ -40,7 +40,7 @@ func NewReaper(lggr logger.Logger, db *sqlx.DB, config ReaperConfig, chainID big db, config, *utils.NewBig(&chainID), - lggr.Named("bptxm_reaper"), + lggr.Named("txm_reaper"), atomic.NewInt64(-1), make(chan struct{}, 1), make(chan struct{}), @@ -50,20 +50,20 @@ func NewReaper(lggr logger.Logger, db *sqlx.DB, config ReaperConfig, chainID big // Start the reaper. Should only be called once. func (r *Reaper) Start() { - r.log.Debugf("BPTXMReaper: started with age threshold %v and interval %v", r.config.EthTxReaperThreshold(), r.config.EthTxReaperInterval()) + r.log.Debugf("TxmReaper: started with age threshold %v and interval %v", r.config.EthTxReaperThreshold(), r.config.EthTxReaperInterval()) go r.runLoop() } // Stop the reaper. Should only be called once. func (r *Reaper) Stop() { - r.log.Debug("BPTXMReaper: stopping") + r.log.Debug("TxmReaper: stopping") close(r.chStop) <-r.chDone } func (r *Reaper) runLoop() { defer close(r.chDone) - ticker := time.NewTicker(r.config.EthTxReaperInterval()) + ticker := time.NewTicker(utils.WithJitter(r.config.EthTxReaperInterval())) defer ticker.Stop() for { select { @@ -71,8 +71,10 @@ func (r *Reaper) runLoop() { return case <-ticker.C: r.work() + ticker.Reset(utils.WithJitter(r.config.EthTxReaperInterval())) case <-r.trigger: r.work() + ticker.Reset(utils.WithJitter(r.config.EthTxReaperInterval())) } } } @@ -84,7 +86,7 @@ func (r *Reaper) work() { } err := r.ReapEthTxes(latestBlockNum) if err != nil { - r.log.Error("BPTXMReaper: unable to reap old eth_txes: ", err) + r.log.Error("TxmReaper: unable to reap old eth_txes: ", err) } } @@ -104,14 +106,14 @@ func (r *Reaper) SetLatestBlockNum(latestBlockNum int64) { func (r *Reaper) ReapEthTxes(headNum int64) error { threshold := r.config.EthTxReaperThreshold() if threshold == 0 { - r.log.Debug("BPTXMReaper: ETH_TX_REAPER_THRESHOLD set to 0; skipping ReapEthTxes") + r.log.Debug("TxmReaper: ETH_TX_REAPER_THRESHOLD set to 0; skipping ReapEthTxes") return nil } minBlockNumberToKeep := headNum - int64(r.config.EvmFinalityDepth()) mark := time.Now() timeThreshold := mark.Add(-threshold) - r.log.Debugw(fmt.Sprintf("BPTXMReaper: reaping old eth_txes created before %s", timeThreshold.Format(time.RFC3339)), "ageThreshold", threshold, "timeThreshold", timeThreshold, "minBlockNumberToKeep", minBlockNumberToKeep) + r.log.Debugw(fmt.Sprintf("TxmReaper: reaping old eth_txes created before %s", timeThreshold.Format(time.RFC3339)), "ageThreshold", threshold, "timeThreshold", timeThreshold, "minBlockNumberToKeep", minBlockNumberToKeep) // Delete old confirmed eth_txes // NOTE that this relies on foreign key triggers automatically removing @@ -141,7 +143,7 @@ AND evm_chain_id = $4`, minBlockNumberToKeep, limit, timeThreshold, r.chainID) return uint(rowsAffected), err }) if err != nil { - return errors.Wrap(err, "BPTXMReaper#reapEthTxes batch delete of confirmed eth_txes failed") + return errors.Wrap(err, "TxmReaper#reapEthTxes batch delete of confirmed eth_txes failed") } // Delete old 'fatal_error' eth_txes err = pg.Batch(func(_, limit uint) (count uint, err error) { @@ -160,10 +162,10 @@ AND evm_chain_id = $2`, timeThreshold, r.chainID) return uint(rowsAffected), err }) if err != nil { - return errors.Wrap(err, "BPTXMReaper#reapEthTxes batch delete of fatally errored eth_txes failed") + return errors.Wrap(err, "TxmReaper#reapEthTxes batch delete of fatally errored eth_txes failed") } - r.log.Debugf("BPTXMReaper: ReapEthTxes completed in %v", time.Since(mark)) + r.log.Debugf("TxmReaper: ReapEthTxes completed in %v", time.Since(mark)) return nil } diff --git a/core/chains/evm/bulletprooftxmanager/reaper_test.go b/core/chains/evm/txmgr/reaper_test.go similarity index 87% rename from core/chains/evm/bulletprooftxmanager/reaper_test.go rename to core/chains/evm/txmgr/reaper_test.go index 55fffac11e6..9c0b216104e 100644 --- a/core/chains/evm/bulletprooftxmanager/reaper_test.go +++ b/core/chains/evm/txmgr/reaper_test.go @@ -1,12 +1,12 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "math/big" "testing" "time" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" @@ -16,11 +16,11 @@ import ( "github.com/stretchr/testify/require" ) -func newReaperWithChainID(t *testing.T, db *sqlx.DB, cfg bulletprooftxmanager.ReaperConfig, cid big.Int) *bulletprooftxmanager.Reaper { - return bulletprooftxmanager.NewReaper(logger.TestLogger(t), db, cfg, cid) +func newReaperWithChainID(t *testing.T, db *sqlx.DB, cfg txmgr.ReaperConfig, cid big.Int) *txmgr.Reaper { + return txmgr.NewReaper(logger.TestLogger(t), db, cfg, cid) } -func newReaper(t *testing.T, db *sqlx.DB, cfg bulletprooftxmanager.ReaperConfig) *bulletprooftxmanager.Reaper { +func newReaper(t *testing.T, db *sqlx.DB, cfg txmgr.ReaperConfig) *txmgr.Reaper { return newReaperWithChainID(t, db, cfg, cltest.FixtureChainID) } @@ -29,7 +29,7 @@ func TestReaper_ReapEthTxes(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, from := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) diff --git a/core/chains/evm/bulletprooftxmanager/strategies.go b/core/chains/evm/txmgr/strategies.go similarity index 98% rename from core/chains/evm/bulletprooftxmanager/strategies.go rename to core/chains/evm/txmgr/strategies.go index 37515af98fe..6148664115b 100644 --- a/core/chains/evm/bulletprooftxmanager/strategies.go +++ b/core/chains/evm/txmgr/strategies.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "github.com/pkg/errors" diff --git a/core/chains/evm/bulletprooftxmanager/strategies_test.go b/core/chains/evm/txmgr/strategies_test.go similarity index 84% rename from core/chains/evm/bulletprooftxmanager/strategies_test.go rename to core/chains/evm/txmgr/strategies_test.go index f9de671ed32..932468b816e 100644 --- a/core/chains/evm/bulletprooftxmanager/strategies_test.go +++ b/core/chains/evm/txmgr/strategies_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "testing" @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" ) @@ -15,7 +15,7 @@ import ( func Test_SendEveryStrategy(t *testing.T) { t.Parallel() - s := bulletprooftxmanager.SendEveryStrategy{} + s := txmgr.SendEveryStrategy{} assert.Equal(t, uuid.NullUUID{}, s.Subject()) @@ -28,7 +28,7 @@ func Test_DropOldestStrategy_Subject(t *testing.T) { t.Parallel() subject := uuid.NewV4() - s := bulletprooftxmanager.NewDropOldestStrategy(subject, 1) + s := txmgr.NewDropOldestStrategy(subject, 1) assert.True(t, s.Subject().Valid) assert.Equal(t, subject, s.Subject().UUID) @@ -39,7 +39,7 @@ func Test_DropOldestStrategy_PruneQueue(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() subj1 := uuid.NewV4() @@ -57,7 +57,7 @@ func Test_DropOldestStrategy_PruneQueue(t *testing.T) { n++ cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, n, fromAddress) n++ - initialEtxs := []bulletprooftxmanager.EthTx{ + initialEtxs := []txmgr.EthTx{ cltest.MustInsertUnstartedEthTx(t, borm, fromAddress, subj1), cltest.MustInsertUnstartedEthTx(t, borm, fromAddress, subj2), cltest.MustInsertUnstartedEthTx(t, borm, otherAddress, subj1), @@ -66,7 +66,7 @@ func Test_DropOldestStrategy_PruneQueue(t *testing.T) { } t.Run("with queue size of 2, removes everything except the newest two transactions for the given subject, ignoring fromAddress", func(t *testing.T) { - s := bulletprooftxmanager.NewDropOldestStrategy(subj1, 2) + s := txmgr.NewDropOldestStrategy(subj1, 2) n, err := s.PruneQueue(db) require.NoError(t, err) @@ -75,7 +75,7 @@ func Test_DropOldestStrategy_PruneQueue(t *testing.T) { // Total inserted was 9. Minus the 2 oldest unstarted makes 7 cltest.AssertCount(t, db, "eth_txes", 7) - var etxs []bulletprooftxmanager.EthTx + var etxs []txmgr.EthTx require.NoError(t, db.Select(&etxs, `SELECT * FROM eth_txes WHERE state = 'unstarted' ORDER BY id asc`)) require.Len(t, etxs, 3) diff --git a/core/chains/evm/bulletprooftxmanager/transmitchecker.go b/core/chains/evm/txmgr/transmitchecker.go similarity index 99% rename from core/chains/evm/bulletprooftxmanager/transmitchecker.go rename to core/chains/evm/txmgr/transmitchecker.go index 03ad9d112c5..f4c64a34cdc 100644 --- a/core/chains/evm/bulletprooftxmanager/transmitchecker.go +++ b/core/chains/evm/txmgr/transmitchecker.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "context" diff --git a/core/chains/evm/bulletprooftxmanager/transmitchecker_test.go b/core/chains/evm/txmgr/transmitchecker_test.go similarity index 73% rename from core/chains/evm/bulletprooftxmanager/transmitchecker_test.go rename to core/chains/evm/txmgr/transmitchecker_test.go index 41aabab6659..cfc0397e337 100644 --- a/core/chains/evm/bulletprooftxmanager/transmitchecker_test.go +++ b/core/chains/evm/txmgr/transmitchecker_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "context" @@ -15,8 +15,8 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" v1 "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -25,43 +25,43 @@ import ( ) func TestFactory(t *testing.T) { - client, _, _ := cltest.NewEthMocksWithDefaultChain(t) - factory := &bulletprooftxmanager.CheckerFactory{Client: client} + client, _ := cltest.NewEthMocksWithDefaultChain(t) + factory := &txmgr.CheckerFactory{Client: client} t.Run("no checker", func(t *testing.T) { - c, err := factory.BuildChecker(bulletprooftxmanager.TransmitCheckerSpec{}) + c, err := factory.BuildChecker(txmgr.TransmitCheckerSpec{}) require.NoError(t, err) - require.Equal(t, bulletprooftxmanager.NoChecker, c) + require.Equal(t, txmgr.NoChecker, c) }) t.Run("vrf v1 checker", func(t *testing.T) { - c, err := factory.BuildChecker(bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeVRFV1, + c, err := factory.BuildChecker(txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeVRFV1, VRFCoordinatorAddress: testutils.NewAddress(), }) require.NoError(t, err) - require.IsType(t, &bulletprooftxmanager.VRFV1Checker{}, c) + require.IsType(t, &txmgr.VRFV1Checker{}, c) }) t.Run("vrf v2 checker", func(t *testing.T) { - c, err := factory.BuildChecker(bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeVRFV2, + c, err := factory.BuildChecker(txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: testutils.NewAddress(), }) require.NoError(t, err) - require.IsType(t, &bulletprooftxmanager.VRFV2Checker{}, c) + require.IsType(t, &txmgr.VRFV2Checker{}, c) }) t.Run("simulate checker", func(t *testing.T) { - c, err := factory.BuildChecker(bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + c, err := factory.BuildChecker(txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, }) require.NoError(t, err) - require.Equal(t, &bulletprooftxmanager.SimulateChecker{Client: client}, c) + require.Equal(t, &txmgr.SimulateChecker{Client: client}, c) }) t.Run("invalid checker type", func(t *testing.T) { - _, err := factory.BuildChecker(bulletprooftxmanager.TransmitCheckerSpec{ + _, err := factory.BuildChecker(txmgr.TransmitCheckerSpec{ CheckerType: "invalid", }) require.EqualError(t, err, "unrecognized checker type: invalid") @@ -74,27 +74,27 @@ func TestTransmitCheckers(t *testing.T) { ctx := context.Background() t.Run("no checker", func(t *testing.T) { - checker := bulletprooftxmanager.NoChecker - require.NoError(t, checker.Check(ctx, log, bulletprooftxmanager.EthTx{}, bulletprooftxmanager.EthTxAttempt{})) + checker := txmgr.NoChecker + require.NoError(t, checker.Check(ctx, log, txmgr.EthTx{}, txmgr.EthTxAttempt{})) }) t.Run("simulate", func(t *testing.T) { - checker := bulletprooftxmanager.SimulateChecker{Client: client} + checker := txmgr.SimulateChecker{Client: client} - tx := bulletprooftxmanager.EthTx{ + tx := txmgr.EthTx{ FromAddress: common.HexToAddress("0xfe0629509E6CB8dfa7a99214ae58Ceb465d5b5A9"), ToAddress: common.HexToAddress("0xff0Aac13eab788cb9a2D662D3FB661Aa5f58FA21"), EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(642), GasLimit: 1e9, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } - attempt := bulletprooftxmanager.EthTxAttempt{ + attempt := txmgr.EthTxAttempt{ EthTx: tx, Hash: common.Hash{}, CreatedAt: tx.CreatedAt, - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, } t.Run("success", func(t *testing.T) { @@ -142,8 +142,8 @@ func TestTransmitCheckers(t *testing.T) { t.Run("VRF V1", func(t *testing.T) { - newTx := func(t *testing.T, vrfReqID [32]byte) (bulletprooftxmanager.EthTx, bulletprooftxmanager.EthTxAttempt) { - meta := bulletprooftxmanager.EthTxMeta{ + newTx := func(t *testing.T, vrfReqID [32]byte) (txmgr.EthTx, txmgr.EthTxAttempt) { + meta := txmgr.EthTxMeta{ RequestID: common.BytesToHash(vrfReqID[:]), MaxLink: "1000000000000000000", // 1 LINK SubID: 2, @@ -153,21 +153,21 @@ func TestTransmitCheckers(t *testing.T) { require.NoError(t, err) metaJson := datatypes.JSON(b) - tx := bulletprooftxmanager.EthTx{ + tx := txmgr.EthTx{ FromAddress: common.HexToAddress("0xfe0629509E6CB8dfa7a99214ae58Ceb465d5b5A9"), ToAddress: common.HexToAddress("0xff0Aac13eab788cb9a2D662D3FB661Aa5f58FA21"), EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(642), GasLimit: 1e9, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, Meta: &metaJson, } - return tx, bulletprooftxmanager.EthTxAttempt{ + return tx, txmgr.EthTxAttempt{ EthTx: tx, Hash: common.Hash{}, CreatedAt: tx.CreatedAt, - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, } } @@ -175,7 +175,7 @@ func TestTransmitCheckers(t *testing.T) { r2 := [32]byte{2} r3 := [32]byte{3} - checker := bulletprooftxmanager.VRFV1Checker{Callbacks: func(opts *bind.CallOpts, reqID [32]byte) (v1.Callbacks, error) { + checker := txmgr.VRFV1Checker{Callbacks: func(opts *bind.CallOpts, reqID [32]byte) (v1.Callbacks, error) { if reqID == r1 { // Request 1 is already fulfilled return v1.Callbacks{ @@ -210,8 +210,8 @@ func TestTransmitCheckers(t *testing.T) { t.Run("VRF V2", func(t *testing.T) { - newTx := func(t *testing.T, vrfReqID *big.Int) (bulletprooftxmanager.EthTx, bulletprooftxmanager.EthTxAttempt) { - meta := bulletprooftxmanager.EthTxMeta{ + newTx := func(t *testing.T, vrfReqID *big.Int) (txmgr.EthTx, txmgr.EthTxAttempt) { + meta := txmgr.EthTxMeta{ RequestID: common.BytesToHash(vrfReqID.Bytes()), MaxLink: "1000000000000000000", // 1 LINK SubID: 2, @@ -221,25 +221,25 @@ func TestTransmitCheckers(t *testing.T) { require.NoError(t, err) metaJson := datatypes.JSON(b) - tx := bulletprooftxmanager.EthTx{ + tx := txmgr.EthTx{ FromAddress: common.HexToAddress("0xfe0629509E6CB8dfa7a99214ae58Ceb465d5b5A9"), ToAddress: common.HexToAddress("0xff0Aac13eab788cb9a2D662D3FB661Aa5f58FA21"), EncodedPayload: []byte{42, 0, 0}, Value: assets.NewEthValue(642), GasLimit: 1e9, CreatedAt: time.Unix(0, 0), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, Meta: &metaJson, } - return tx, bulletprooftxmanager.EthTxAttempt{ + return tx, txmgr.EthTxAttempt{ EthTx: tx, Hash: common.Hash{}, CreatedAt: tx.CreatedAt, - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, } } - checker := bulletprooftxmanager.VRFV2Checker{GetCommitment: func(_ *bind.CallOpts, requestID *big.Int) ([32]byte, error) { + checker := txmgr.VRFV2Checker{GetCommitment: func(_ *bind.CallOpts, requestID *big.Int) ([32]byte, error) { if requestID.String() == "1" { // Request 1 is already fulfilled return [32]byte{}, nil diff --git a/core/chains/evm/bulletprooftxmanager/bulletprooftxmanager.go b/core/chains/evm/txmgr/txmgr.go similarity index 85% rename from core/chains/evm/bulletprooftxmanager/bulletprooftxmanager.go rename to core/chains/evm/txmgr/txmgr.go index a3ae66b90b5..8e937cb21dc 100644 --- a/core/chains/evm/bulletprooftxmanager/bulletprooftxmanager.go +++ b/core/chains/evm/txmgr/txmgr.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager +package txmgr import ( "bytes" @@ -31,7 +31,7 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -// Config encompasses config used by bulletprooftxmanager package +// Config encompasses config used by txmgr package // Unless otherwise specified, these should support changing at runtime //go:generate mockery --recursive --name Config --output ./mocks/ --case=underscore --structname Config --filename config.go type Config interface { @@ -51,17 +51,17 @@ type Config interface { LogSQL() bool } -// KeyStore encompasses the subset of keystore used by bulletprooftxmanager +// KeyStore encompasses the subset of keystore used by txmgr type KeyStore interface { GetStatesForChain(chainID *big.Int) ([]ethkey.State, error) SignTx(fromAddress common.Address, tx *gethTypes.Transaction, chainID *big.Int) (*gethTypes.Transaction, error) SubscribeToKeyChanges() (ch chan struct{}, unsub func()) } -// For more information about the BulletproofTxManager architecture, see the design doc: -// https://www.notion.so/chainlink/BulletproofTxManager-Architecture-Overview-9dc62450cd7a443ba9e7dceffa1a8d6b +// For more information about the Txm architecture, see the design doc: +// https://www.notion.so/chainlink/Txm-Architecture-Overview-9dc62450cd7a443ba9e7dceffa1a8d6b -var _ TxManager = &BulletproofTxManager{} +var _ TxManager = &Txm{} // ResumeCallback is assumed to be idempotent type ResumeCallback func(id uuid.UUID, result interface{}, err error) error @@ -69,7 +69,7 @@ type ResumeCallback func(id uuid.UUID, result interface{}, err error) error //go:generate mockery --recursive --name TxManager --output ./mocks/ --case=underscore --structname TxManager --filename tx_manager.go type TxManager interface { httypes.HeadTrackable - services.Service + services.ServiceCtx Trigger(addr common.Address) CreateEthTransaction(newTx NewTx, qopts ...pg.QOpt) (etx EthTx, err error) GetGasEstimator() gas.Estimator @@ -77,7 +77,7 @@ type TxManager interface { SendEther(chainID *big.Int, from, to common.Address, value assets.Eth, gasLimit uint64) (etx EthTx, err error) } -type BulletproofTxManager struct { +type Txm struct { utils.StartStopOnce logger logger.Logger @@ -103,13 +103,13 @@ type BulletproofTxManager struct { ethResender *EthResender } -func (b *BulletproofTxManager) RegisterResumeCallback(fn ResumeCallback) { +func (b *Txm) RegisterResumeCallback(fn ResumeCallback) { b.resumeCallback = fn } -// NewBulletproofTxManager creates a new BulletproofTxManager with the given configuration. -func NewBulletproofTxManager(db *sqlx.DB, ethClient evmclient.Client, cfg Config, keyStore KeyStore, eventBroadcaster pg.EventBroadcaster, lggr logger.Logger, checkerFactory TransmitCheckerFactory) *BulletproofTxManager { - lggr = lggr.Named("BulletproofTxManager") +// NewTxm creates a new Txm with the given configuration. +func NewTxm(db *sqlx.DB, ethClient evmclient.Client, cfg Config, keyStore KeyStore, eventBroadcaster pg.EventBroadcaster, lggr logger.Logger, checkerFactory TransmitCheckerFactory) *Txm { + lggr = lggr.Named("Txm") lggr.Infow("Initializing EVM transaction manager", "gasBumpTxDepth", cfg.EvmGasBumpTxDepth(), "maxInFlightTransactions", cfg.EvmMaxInFlightTransactions(), @@ -117,7 +117,7 @@ func NewBulletproofTxManager(db *sqlx.DB, ethClient evmclient.Client, cfg Config "nonceAutoSync", cfg.EvmNonceAutoSync(), "gasLimitDefault", cfg.EvmGasLimitDefault(), ) - b := BulletproofTxManager{ + b := Txm{ StartStopOnce: utils.StartStopOnce{}, logger: lggr, db: db, @@ -148,11 +148,13 @@ func NewBulletproofTxManager(db *sqlx.DB, ethClient evmclient.Client, cfg Config return &b } -func (b *BulletproofTxManager) Start() (merr error) { - return b.StartOnce("BulletproofTxManager", func() error { +// Start starts Txm service. +// The provided context can be used to terminate Start sequence. +func (b *Txm) Start(ctx context.Context) (merr error) { + return b.StartOnce("Txm", func() error { keyStates, err := b.keyStore.GetStatesForChain(&b.chainID) if err != nil { - return errors.Wrap(err, "BulletproofTxManager: failed to load key states") + return errors.Wrap(err, "Txm: failed to load key states") } if len(keyStates) > 0 { @@ -163,15 +165,15 @@ func (b *BulletproofTxManager) Start() (merr error) { eb := NewEthBroadcaster(b.db, b.ethClient, b.config, b.keyStore, b.eventBroadcaster, keyStates, b.gasEstimator, b.resumeCallback, b.logger, b.checkerFactory) ec := NewEthConfirmer(b.db, b.ethClient, b.config, b.keyStore, keyStates, b.gasEstimator, b.resumeCallback, b.logger) - if err := eb.Start(); err != nil { - return errors.Wrap(err, "BulletproofTxManager: EthBroadcaster failed to start") + if err := eb.Start(ctx); err != nil { + return errors.Wrap(err, "Txm: EthBroadcaster failed to start") } if err := ec.Start(); err != nil { - return errors.Wrap(err, "BulletproofTxManager: EthConfirmer failed to start") + return errors.Wrap(err, "Txm: EthConfirmer failed to start") } - if err := b.gasEstimator.Start(); err != nil { - return errors.Wrap(err, "BulletproofTxManager: Estimator failed to start") + if err := b.gasEstimator.Start(ctx); err != nil { + return errors.Wrap(err, "Txm: Estimator failed to start") } b.wg.Add(1) @@ -190,8 +192,8 @@ func (b *BulletproofTxManager) Start() (merr error) { }) } -func (b *BulletproofTxManager) Close() (merr error) { - return b.StopOnce("BulletproofTxManager", func() error { +func (b *Txm) Close() (merr error) { + return b.StopOnce("Txm", func() error { close(b.chStop) if b.reaper != nil { @@ -209,13 +211,16 @@ func (b *BulletproofTxManager) Close() (merr error) { }) } -func (b *BulletproofTxManager) runLoop(eb *EthBroadcaster, ec *EthConfirmer) { +func (b *Txm) runLoop(eb *EthBroadcaster, ec *EthConfirmer) { defer b.wg.Done() keysChanged, unsub := b.keyStore.SubscribeToKeyChanges() defer unsub() close(b.chSubbed) + ctx, cancel := utils.ContextFromChan(b.chStop) + defer cancel() + for { select { case address := <-b.trigger: @@ -240,18 +245,18 @@ func (b *BulletproofTxManager) runLoop(eb *EthBroadcaster, ec *EthConfirmer) { eb = NewEthBroadcaster(b.db, b.ethClient, b.config, b.keyStore, b.eventBroadcaster, keyStates, b.gasEstimator, b.resumeCallback, b.logger, b.checkerFactory) ec = NewEthConfirmer(b.db, b.ethClient, b.config, b.keyStore, keyStates, b.gasEstimator, b.resumeCallback, b.logger) - if err := eb.Start(); err != nil { - b.logger.Errorw("Failed to start EthBroadcaster", "error", err) + if err := eb.Start(ctx); err != nil { + b.logger.Criticalw("Failed to start EthBroadcaster", "error", err) } if err := ec.Start(); err != nil { - b.logger.Errorw("Failed to start EthConfirmer", "error", err) + b.logger.Criticalw("Failed to start EthConfirmer", "error", err) } } } } // OnNewLongestChain conforms to HeadTrackable -func (b *BulletproofTxManager) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { +func (b *Txm) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { ok := b.IfStarted(func() { if b.reaper != nil { b.reaper.SetLatestBlockNum(head.Number) @@ -269,7 +274,7 @@ func (b *BulletproofTxManager) OnNewLongestChain(ctx context.Context, head *evmt } // Trigger forces the EthBroadcaster to check early for the given address -func (b *BulletproofTxManager) Trigger(addr common.Address) { +func (b *Txm) Trigger(addr common.Address) { select { case b.trigger <- addr: default: @@ -293,12 +298,12 @@ type NewTx struct { } // CreateEthTransaction inserts a new transaction -func (b *BulletproofTxManager) CreateEthTransaction(newTx NewTx, qs ...pg.QOpt) (etx EthTx, err error) { +func (b *Txm) CreateEthTransaction(newTx NewTx, qs ...pg.QOpt) (etx EthTx, err error) { q := b.q.WithOpts(qs...) err = CheckEthTxQueueCapacity(q, newTx.FromAddress, b.config.EvmMaxQueuedTransactions(), b.chainID) if err != nil { - return etx, errors.Wrap(err, "BulletproofTxManager#CreateEthTransaction") + return etx, errors.Wrap(err, "Txm#CreateEthTransaction") } value := 0 @@ -308,7 +313,7 @@ func (b *BulletproofTxManager) CreateEthTransaction(newTx NewTx, qs ...pg.QOpt) // If no eth_tx matches (the common case) then continue if !errors.Is(err, sql.ErrNoRows) { if err != nil { - return errors.Wrap(err, "BulletproofTxManager#CreateEthTransaction") + return errors.Wrap(err, "Txm#CreateEthTransaction") } // if a previous transaction for this task run exists, immediately return it return nil @@ -325,12 +330,12 @@ $1,$2,$3,$4,$5,'unstarted',NOW(),$6,$7,$8,$9,$10,$11 RETURNING "eth_txes".* `, newTx.FromAddress, newTx.ToAddress, newTx.EncodedPayload, value, newTx.GasLimit, newTx.Meta, newTx.Strategy.Subject(), b.chainID.String(), newTx.MinConfirmations, newTx.PipelineTaskRunID, newTx.Checker) if err != nil { - return errors.Wrap(err, "BulletproofTxManager#CreateEthTransaction failed to insert eth_tx") + return errors.Wrap(err, "Txm#CreateEthTransaction failed to insert eth_tx") } pruned, err := newTx.Strategy.PruneQueue(tx) if err != nil { - return errors.Wrap(err, "BulletproofTxManager#CreateEthTransaction failed to prune eth_txes") + return errors.Wrap(err, "Txm#CreateEthTransaction failed to prune eth_txes") } if pruned > 0 { b.logger.Warnw(fmt.Sprintf("Dropped %d old transactions from transaction queue", pruned), "fromAddress", newTx.FromAddress, "toAddress", newTx.ToAddress, "meta", newTx.Meta, "subject", newTx.Strategy.Subject(), "replacementID", etx.ID) @@ -340,7 +345,7 @@ RETURNING "eth_txes".* return } -func (b *BulletproofTxManager) checkStateExists(q pg.Queryer, addr common.Address) error { +func (b *Txm) checkStateExists(q pg.Queryer, addr common.Address) error { var state ethkey.State err := q.Get(&state, `SELECT * FROM eth_key_states WHERE address = $1`, addr) if errors.Is(err, sql.ErrNoRows) { @@ -355,12 +360,12 @@ func (b *BulletproofTxManager) checkStateExists(q pg.Queryer, addr common.Addres } // GetGasEstimator returns the gas estimator, mostly useful for tests -func (b *BulletproofTxManager) GetGasEstimator() gas.Estimator { +func (b *Txm) GetGasEstimator() gas.Estimator { return b.gasEstimator } // SendEther creates a transaction that transfers the given value of ether -func (b *BulletproofTxManager) SendEther(chainID *big.Int, from, to common.Address, value assets.Eth, gasLimit uint64) (etx EthTx, err error) { +func (b *Txm) SendEther(chainID *big.Int, from, to common.Address, value assets.Eth, gasLimit uint64) (etx EthTx, err error) { if to == utils.ZeroAddress { return etx, errors.New("cannot send ether to zero address") } @@ -520,7 +525,7 @@ func CheckEthTxQueueCapacity(q pg.Queryer, fromAddress common.Address, maxQueued var count uint64 err = q.Get(&count, `SELECT count(*) FROM eth_txes WHERE from_address = $1 AND state = 'unstarted' AND evm_chain_id = $2`, fromAddress, chainID.String()) if err != nil { - err = errors.Wrap(err, "bulletprooftxmanager.CheckEthTxQueueCapacity query failed") + err = errors.Wrap(err, "txmgr.CheckEthTxQueueCapacity query failed") return } @@ -537,9 +542,15 @@ type NullTxManager struct { } func (n *NullTxManager) OnNewLongestChain(context.Context, *evmtypes.Head) {} -func (n *NullTxManager) Start() error { return nil } -func (n *NullTxManager) Close() error { return nil } -func (n *NullTxManager) Trigger(common.Address) { panic(n.ErrMsg) } + +// Start does noop for NullTxManager. +func (n *NullTxManager) Start(context.Context) error { return nil } + +// Close does noop for NullTxManager. +func (n *NullTxManager) Close() error { return nil } + +// Trigger does noop for NullTxManager. +func (n *NullTxManager) Trigger(common.Address) { panic(n.ErrMsg) } func (n *NullTxManager) CreateEthTransaction(NewTx, ...pg.QOpt) (etx EthTx, err error) { return etx, errors.New(n.ErrMsg) } diff --git a/core/chains/evm/bulletprooftxmanager/bulletprooftxmanager_test.go b/core/chains/evm/txmgr/txmgr_test.go similarity index 76% rename from core/chains/evm/bulletprooftxmanager/bulletprooftxmanager_test.go rename to core/chains/evm/txmgr/txmgr_test.go index 91e2643d4b4..980c2e13b4a 100644 --- a/core/chains/evm/bulletprooftxmanager/bulletprooftxmanager_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -1,4 +1,4 @@ -package bulletprooftxmanager_test +package txmgr_test import ( "context" @@ -17,8 +17,8 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/chains" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -30,7 +30,7 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -func TestBulletproofTxManager_SendEther_DoesNotSendToZero(t *testing.T) { +func TestTxm_SendEther_DoesNotSendToZero(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) @@ -47,19 +47,19 @@ func TestBulletproofTxManager_SendEther_DoesNotSendToZero(t *testing.T) { lggr := logger.TestLogger(t) checkerFactory := &testCheckerFactory{} - bptxm := bulletprooftxmanager.NewBulletproofTxManager(db, ethClient, config, nil, nil, lggr, checkerFactory) + txm := txmgr.NewTxm(db, ethClient, config, nil, nil, lggr, checkerFactory) - _, err := bptxm.SendEther(big.NewInt(0), from, to, *value, 21000) + _, err := txm.SendEther(big.NewInt(0), from, to, *value, 21000) require.Error(t, err) require.EqualError(t, err, "cannot send ether to zero address") } -func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { +func TestTxm_CheckEthTxQueueCapacity(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) @@ -68,7 +68,7 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { var maxUnconfirmedTransactions uint64 = 2 t.Run("with no eth_txes returns nil", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.NoError(t, err) }) @@ -78,7 +78,7 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { } t.Run("with eth_txes from another address returns nil", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.NoError(t, err) }) @@ -87,7 +87,7 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { } t.Run("ignores fatally_errored transactions", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.NoError(t, err) }) @@ -98,7 +98,7 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { n++ t.Run("unconfirmed and in_progress transactions do not count", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, 1, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, 1, cltest.FixtureChainID) require.NoError(t, err) }) @@ -109,7 +109,7 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { } t.Run("with many confirmed eth_txes from the same address returns nil", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.NoError(t, err) }) @@ -118,41 +118,41 @@ func TestBulletproofTxManager_CheckEthTxQueueCapacity(t *testing.T) { } t.Run("with fewer unstarted eth_txes than limit returns nil", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.NoError(t, err) }) cltest.MustInsertUnstartedEthTx(t, borm, fromAddress) t.Run("with equal or more unstarted eth_txes than limit returns error", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.Error(t, err) require.EqualError(t, err, fmt.Sprintf("cannot create transaction; too many unstarted transactions in the queue (2/%d). WARNING: Hitting ETH_MAX_QUEUED_TRANSACTIONS is a sanity limit and should never happen under normal operation. This error is very unlikely to be a problem with Chainlink, and instead more likely to be caused by a problem with your eth node's connectivity. Check your eth node: it may not be broadcasting transactions to the network, or it might be overloaded and evicting Chainlink's transactions from its mempool. Increasing ETH_MAX_QUEUED_TRANSACTIONS is almost certainly not the correct action to take here unless you ABSOLUTELY know what you are doing, and will probably make things worse", maxUnconfirmedTransactions)) cltest.MustInsertUnstartedEthTx(t, borm, fromAddress) - err = bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) + err = txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, cltest.FixtureChainID) require.Error(t, err) require.EqualError(t, err, fmt.Sprintf("cannot create transaction; too many unstarted transactions in the queue (3/%d). WARNING: Hitting ETH_MAX_QUEUED_TRANSACTIONS is a sanity limit and should never happen under normal operation. This error is very unlikely to be a problem with Chainlink, and instead more likely to be caused by a problem with your eth node's connectivity. Check your eth node: it may not be broadcasting transactions to the network, or it might be overloaded and evicting Chainlink's transactions from its mempool. Increasing ETH_MAX_QUEUED_TRANSACTIONS is almost certainly not the correct action to take here unless you ABSOLUTELY know what you are doing, and will probably make things worse", maxUnconfirmedTransactions)) }) t.Run("with different chain ID ignores txes", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, *big.NewInt(42)) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, maxUnconfirmedTransactions, *big.NewInt(42)) require.NoError(t, err) }) t.Run("disables check with 0 limit", func(t *testing.T) { - err := bulletprooftxmanager.CheckEthTxQueueCapacity(db, fromAddress, 0, cltest.FixtureChainID) + err := txmgr.CheckEthTxQueueCapacity(db, fromAddress, 0, cltest.FixtureChainID) require.NoError(t, err) }) } -func TestBulletproofTxManager_CountUnconfirmedTransactions(t *testing.T) { +func TestTxm_CountUnconfirmedTransactions(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -164,17 +164,17 @@ func TestBulletproofTxManager_CountUnconfirmedTransactions(t *testing.T) { cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 2, fromAddress) q := pg.NewQ(db, logger.TestLogger(t), cfg) - count, err := bulletprooftxmanager.CountUnconfirmedTransactions(q, fromAddress, cltest.FixtureChainID) + count, err := txmgr.CountUnconfirmedTransactions(q, fromAddress, cltest.FixtureChainID) require.NoError(t, err) assert.Equal(t, int(count), 3) } -func TestBulletproofTxManager_CountUnstartedTransactions(t *testing.T) { +func TestTxm_CountUnstartedTransactions(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -186,16 +186,16 @@ func TestBulletproofTxManager_CountUnstartedTransactions(t *testing.T) { cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 2, fromAddress) q := pg.NewQ(db, logger.TestLogger(t), cfg) - count, err := bulletprooftxmanager.CountUnstartedTransactions(q, fromAddress, cltest.FixtureChainID) + count, err := txmgr.CountUnstartedTransactions(q, fromAddress, cltest.FixtureChainID) require.NoError(t, err) assert.Equal(t, int(count), 2) } -func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { +func TestTxm_CreateEthTransaction(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) keyStore := cltest.NewKeyStore(t, db, cfg) _, fromAddress := cltest.MustInsertRandomKey(t, keyStore.Eth(), 0) @@ -212,7 +212,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { lggr := logger.TestLogger(t) checkerFactory := &testCheckerFactory{} - bptxm := bulletprooftxmanager.NewBulletproofTxManager(db, ethClient, config, nil, nil, lggr, checkerFactory) + txm := txmgr.NewTxm(db, ethClient, config, nil, nil, lggr, checkerFactory) t.Run("with queue under capacity inserts eth_tx", func(t *testing.T) { subject := uuid.NewV4() @@ -220,7 +220,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { strategy.On("Subject").Return(uuid.NullUUID{UUID: subject, Valid: true}) strategy.On("PruneQueue", mock.AnythingOfType("*sqlx.Tx")).Return(int64(0), nil) config.On("EvmMaxQueuedTransactions").Return(uint64(1)).Once() - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, @@ -231,7 +231,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { assert.NoError(t, err) assert.Greater(t, etx.ID, int64(0)) - assert.Equal(t, etx.State, bulletprooftxmanager.EthTxUnstarted) + assert.Equal(t, etx.State, txmgr.EthTxUnstarted) assert.Equal(t, gasLimit, etx.GasLimit) assert.Equal(t, fromAddress, etx.FromAddress) assert.Equal(t, toAddress, etx.ToAddress) @@ -243,7 +243,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { require.NoError(t, db.Get(&etx, `SELECT * FROM eth_txes ORDER BY id ASC LIMIT 1`)) - assert.Equal(t, etx.State, bulletprooftxmanager.EthTxUnstarted) + assert.Equal(t, etx.State, txmgr.EthTxUnstarted) assert.Equal(t, gasLimit, etx.GasLimit) assert.Equal(t, fromAddress, etx.FromAddress) assert.Equal(t, toAddress, etx.ToAddress) @@ -256,38 +256,38 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { t.Run("with queue at capacity does not insert eth_tx", func(t *testing.T) { config.On("EvmMaxQueuedTransactions").Return(uint64(1)).Once() - _, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, GasLimit: 21000, Meta: nil, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, + Strategy: txmgr.SendEveryStrategy{}, }) - assert.EqualError(t, err, "BulletproofTxManager#CreateEthTransaction: cannot create transaction; too many unstarted transactions in the queue (1/1). WARNING: Hitting ETH_MAX_QUEUED_TRANSACTIONS is a sanity limit and should never happen under normal operation. This error is very unlikely to be a problem with Chainlink, and instead more likely to be caused by a problem with your eth node's connectivity. Check your eth node: it may not be broadcasting transactions to the network, or it might be overloaded and evicting Chainlink's transactions from its mempool. Increasing ETH_MAX_QUEUED_TRANSACTIONS is almost certainly not the correct action to take here unless you ABSOLUTELY know what you are doing, and will probably make things worse") + assert.EqualError(t, err, "Txm#CreateEthTransaction: cannot create transaction; too many unstarted transactions in the queue (1/1). WARNING: Hitting ETH_MAX_QUEUED_TRANSACTIONS is a sanity limit and should never happen under normal operation. This error is very unlikely to be a problem with Chainlink, and instead more likely to be caused by a problem with your eth node's connectivity. Check your eth node: it may not be broadcasting transactions to the network, or it might be overloaded and evicting Chainlink's transactions from its mempool. Increasing ETH_MAX_QUEUED_TRANSACTIONS is almost certainly not the correct action to take here unless you ABSOLUTELY know what you are doing, and will probably make things worse") }) t.Run("doesn't insert eth_tx if a matching tx already exists for that pipeline_task_run_id", func(t *testing.T) { config.On("EvmMaxQueuedTransactions").Return(uint64(3)).Once() id := uuid.NewV4() - tx1, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + tx1, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, GasLimit: 21000, PipelineTaskRunID: &id, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, + Strategy: txmgr.SendEveryStrategy{}, }) assert.NoError(t, err) config.On("EvmMaxQueuedTransactions").Return(uint64(3)).Once() - tx2, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + tx2, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, GasLimit: 21000, PipelineTaskRunID: &id, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, + Strategy: txmgr.SendEveryStrategy{}, }) assert.NoError(t, err) @@ -297,24 +297,24 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { t.Run("returns error if eth key state is missing or doesn't match chain ID", func(t *testing.T) { config.On("EvmMaxQueuedTransactions").Return(uint64(3)).Twice() rndAddr := testutils.NewAddress() - _, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: rndAddr, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, GasLimit: 21000, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, + Strategy: txmgr.SendEveryStrategy{}, }) require.Error(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("no eth key exists with address %s", rndAddr.Hex())) _, otherAddress := cltest.MustInsertRandomKey(t, keyStore.Eth(), *utils.NewBigI(1337), 0) - _, err = bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err = txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: otherAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, GasLimit: 21000, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, + Strategy: txmgr.SendEveryStrategy{}, }) require.Error(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("cannot send transaction on chain ID 0; eth key with address %s is pegged to chain ID 1337", otherAddress.Hex())) @@ -323,16 +323,16 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { t.Run("simulate transmit checker", func(t *testing.T) { pgtest.MustExec(t, db, `DELETE FROM eth_txes`) - checker := bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeSimulate, + checker := txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeSimulate, } config.On("EvmMaxQueuedTransactions").Return(uint64(1)).Once() - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, GasLimit: gasLimit, - Strategy: bulletprooftxmanager.NewSendEveryStrategy(), + Strategy: txmgr.NewSendEveryStrategy(), Checker: checker, }) assert.NoError(t, err) @@ -340,7 +340,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { require.NoError(t, db.Get(&etx, `SELECT * FROM eth_txes ORDER BY id ASC LIMIT 1`)) - var c bulletprooftxmanager.TransmitCheckerSpec + var c txmgr.TransmitCheckerSpec require.NotNil(t, etx.TransmitChecker) require.NoError(t, json.Unmarshal(*etx.TransmitChecker, &c)) require.Equal(t, checker, c) @@ -352,7 +352,7 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { jobID := int32(25) requestID := gethcommon.HexToHash("abcd") requestTxHash := gethcommon.HexToHash("dcba") - meta := &bulletprooftxmanager.EthTxMeta{ + meta := &txmgr.EthTxMeta{ JobID: jobID, RequestID: requestID, RequestTxHash: requestTxHash, @@ -360,17 +360,17 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { SubID: 2, } config.On("EvmMaxQueuedTransactions").Return(uint64(1)).Once() - checker := bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeVRFV2, + checker := txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: testutils.NewAddress(), } - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, GasLimit: gasLimit, Meta: meta, - Strategy: bulletprooftxmanager.NewSendEveryStrategy(), + Strategy: txmgr.NewSendEveryStrategy(), Checker: checker, }) assert.NoError(t, err) @@ -382,28 +382,28 @@ func TestBulletproofTxManager_CreateEthTransaction(t *testing.T) { require.NoError(t, err) require.Equal(t, meta, m) - var c bulletprooftxmanager.TransmitCheckerSpec + var c txmgr.TransmitCheckerSpec require.NotNil(t, etx.TransmitChecker) require.NoError(t, json.Unmarshal(*etx.TransmitChecker, &c)) require.Equal(t, checker, c) }) } -func newMockTxStrategy(t *testing.T) *bptxmmocks.TxStrategy { - strategy := new(bptxmmocks.TxStrategy) +func newMockTxStrategy(t *testing.T) *txmmocks.TxStrategy { + strategy := new(txmmocks.TxStrategy) strategy.Test(t) return strategy } -func newMockConfig(t *testing.T) *bptxmmocks.Config { +func newMockConfig(t *testing.T) *txmmocks.Config { // These are only used for logging, the exact value doesn't matter // It can be overridden in the test that uses it - cfg := new(bptxmmocks.Config) + cfg := new(txmmocks.Config) cfg.Test(t) cfg.On("EvmGasBumpTxDepth").Return(uint16(42)).Maybe().Once() cfg.On("EvmMaxInFlightTransactions").Return(uint32(42)).Maybe().Once() cfg.On("EvmMaxQueuedTransactions").Return(uint64(42)).Maybe().Once() - cfg.On("EvmNonceAutoSync").Return(true).Maybe().Once() + cfg.On("EvmNonceAutoSync").Return(true).Maybe() cfg.On("EvmGasLimitDefault").Return(uint64(42)).Maybe().Once() cfg.On("BlockHistoryEstimatorBatchSize").Return(uint32(42)).Maybe().Once() cfg.On("BlockHistoryEstimatorBlockDelay").Return(uint16(42)).Maybe().Once() @@ -422,13 +422,15 @@ func newMockConfig(t *testing.T) *bptxmmocks.Config { cfg.On("EvmMaxGasPriceWei").Return(big.NewInt(42)).Maybe().Once() cfg.On("EvmMinGasPriceWei").Return(big.NewInt(42)).Maybe().Once() + cfg.On("LogSQL").Maybe().Return(false) + return cfg } -func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { +func TestTxm_CreateEthTransaction_OutOfEth(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) etKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() thisKey, _ := cltest.MustInsertRandomKey(t, etKeyStore, 1) @@ -446,7 +448,7 @@ func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { ethClient := cltest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestLogger(t) - bptxm := bulletprooftxmanager.NewBulletproofTxManager(db, ethClient, config, nil, nil, lggr, &testCheckerFactory{}) + txm := txmgr.NewTxm(db, ethClient, config, nil, nil, lggr, &testCheckerFactory{}) t.Run("if another key has any transactions with insufficient eth errors, transmits as normal", func(t *testing.T) { payload := cltest.MustRandomBytes(t, 100) @@ -456,7 +458,7 @@ func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { strategy.On("Subject").Return(uuid.NullUUID{}) strategy.On("PruneQueue", mock.AnythingOfType("*sqlx.Tx")).Return(int64(0), nil) - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, @@ -480,7 +482,7 @@ func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { strategy.On("Subject").Return(uuid.NullUUID{}) strategy.On("PruneQueue", mock.AnythingOfType("*sqlx.Tx")).Return(int64(0), nil) - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, @@ -504,7 +506,7 @@ func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { strategy.On("PruneQueue", mock.AnythingOfType("*sqlx.Tx")).Return(int64(0), nil) config.On("EvmMaxQueuedTransactions").Return(uint64(1)) - etx, err := bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + etx, err := txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, @@ -519,7 +521,7 @@ func TestBulletproofTxManager_CreateEthTransaction_OutOfEth(t *testing.T) { }) } -func TestBulletproofTxManager_Lifecycle(t *testing.T) { +func TestTxm_Lifecycle(t *testing.T) { db := pgtest.NewSqlxDB(t) ethClient := cltest.NewEthClientMockWithDefaultChain(t) @@ -535,7 +537,8 @@ func TestBulletproofTxManager_Lifecycle(t *testing.T) { config.On("EvmMaxInFlightTransactions").Return(uint32(42)) config.On("EvmFinalityDepth").Maybe().Return(uint32(42)) config.On("GasEstimatorMode").Return("FixedPrice") - config.On("LogSQL").Return(false) + config.On("LogSQL").Return(false).Maybe() + config.On("EvmRPCDefaultBatchSize").Return(uint32(4)).Maybe() kst.On("GetStatesForChain", &cltest.FixtureChainID).Return([]ethkey.State{}, nil).Once() keyChangeCh := make(chan struct{}) @@ -543,34 +546,33 @@ func TestBulletproofTxManager_Lifecycle(t *testing.T) { kst.On("SubscribeToKeyChanges").Return(keyChangeCh, unsub.ItHappened) lggr := logger.TestLogger(t) checkerFactory := &testCheckerFactory{} - bptxm := bulletprooftxmanager.NewBulletproofTxManager(db, ethClient, config, kst, eventBroadcaster, lggr, checkerFactory) + txm := txmgr.NewTxm(db, ethClient, config, kst, eventBroadcaster, lggr, checkerFactory) head := cltest.Head(42) // It should not hang or panic - bptxm.OnNewLongestChain(context.Background(), head) + txm.OnNewLongestChain(context.Background(), head) sub := new(pgmocks.Subscription) sub.On("Events").Return(make(<-chan pg.Event)) eventBroadcaster.On("Subscribe", "insert_on_eth_txes", "").Return(sub, nil) - config.On("EvmNonceAutoSync").Return(true) config.On("EvmGasBumpThreshold").Return(uint64(1)) - require.NoError(t, bptxm.Start()) + require.NoError(t, txm.Start(testutils.Context(t))) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bptxm.OnNewLongestChain(ctx, head) + txm.OnNewLongestChain(ctx, head) require.NoError(t, ctx.Err()) keyState := cltest.MustGenerateRandomKeyState(t) kst.On("GetStatesForChain", &cltest.FixtureChainID).Return([]ethkey.State{keyState}, nil).Once() sub.On("Close").Return() - ethClient.On("PendingNonceAt", mock.AnythingOfType("*context.cancelCtx"), keyState.Address.Address()).Return(uint64(0), nil) - config.On("TriggerFallbackDBPollInterval").Return(1 * time.Hour) + ethClient.On("PendingNonceAt", mock.AnythingOfType("*context.cancelCtx"), keyState.Address.Address()).Return(uint64(0), nil).Maybe() + config.On("TriggerFallbackDBPollInterval").Return(1 * time.Hour).Maybe() keyChangeCh <- struct{}{} - require.NoError(t, bptxm.Close()) + require.NoError(t, txm.Close()) ethClient.AssertExpectations(t) config.AssertExpectations(t) @@ -579,7 +581,7 @@ func TestBulletproofTxManager_Lifecycle(t *testing.T) { unsub.AwaitOrFail(t, 1*time.Second) } -func TestBulletproofTxManager_SignTx(t *testing.T) { +func TestTxm_SignTx(t *testing.T) { t.Parallel() addr := gethcommon.HexToAddress("0xb921F7763960b296B9cbAD586ff066A18D749724") @@ -595,13 +597,13 @@ func TestBulletproofTxManager_SignTx(t *testing.T) { t.Run("returns correct hash for non-okex chains", func(t *testing.T) { chainID := big.NewInt(1) - cfg := new(bptxmmocks.Config) + cfg := new(txmmocks.Config) cfg.Test(t) cfg.On("ChainType").Return(chains.ChainType("")) kst := new(ksmocks.Eth) kst.Test(t) kst.On("SignTx", to, tx, chainID).Return(tx, nil).Once() - cks := bulletprooftxmanager.NewChainKeyStore(*chainID, cfg, kst) + cks := txmgr.NewChainKeyStore(*chainID, cfg, kst) hash, rawBytes, err := cks.SignTx(addr, tx) require.NoError(t, err) require.NotNil(t, rawBytes) @@ -609,13 +611,13 @@ func TestBulletproofTxManager_SignTx(t *testing.T) { }) t.Run("returns correct hash for okex chains", func(t *testing.T) { chainID := big.NewInt(1) - cfg := new(bptxmmocks.Config) + cfg := new(txmmocks.Config) cfg.Test(t) cfg.On("ChainType").Return(chains.ExChain) kst := new(ksmocks.Eth) kst.Test(t) kst.On("SignTx", to, tx, chainID).Return(tx, nil).Once() - cks := bulletprooftxmanager.NewChainKeyStore(*chainID, cfg, kst) + cks := txmgr.NewChainKeyStore(*chainID, cfg, kst) hash, rawBytes, err := cks.SignTx(addr, tx) require.NoError(t, err) require.NotNil(t, rawBytes) diff --git a/core/chains/evm/types/models_test.go b/core/chains/evm/types/models_test.go index 21ad3822cb5..94baa9ad667 100644 --- a/core/chains/evm/types/models_test.go +++ b/core/chains/evm/types/models_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -83,7 +83,7 @@ func TestHead_NextInt(t *testing.T) { } func TestEthTx_GetID(t *testing.T) { - tx := bulletprooftxmanager.EthTx{ID: math.MinInt64} + tx := txmgr.EthTx{ID: math.MinInt64} assert.Equal(t, "-9223372036854775808", tx.GetID()) } @@ -102,7 +102,7 @@ func TestEthTxAttempt_GetSignedTx(t *testing.T) { rlp := new(bytes.Buffer) require.NoError(t, signedTx.EncodeRLP(rlp)) - attempt := bulletprooftxmanager.EthTxAttempt{SignedRawTx: rlp.Bytes()} + attempt := txmgr.EthTxAttempt{SignedRawTx: rlp.Bytes()} gotSignedTx, err := attempt.GetSignedTx() require.NoError(t, err) @@ -362,8 +362,8 @@ func Test_NullableEIP2930AccessList(t *testing.T) { jsonStr := fmt.Sprintf(`[{"address":"0x%s","storageKeys":["%s"]}]`, hex.EncodeToString(addr.Bytes()), storageKey.Hex()) require.Equal(t, jsonStr, string(alb)) - nNull := bulletprooftxmanager.NullableEIP2930AccessList{} - nValid := bulletprooftxmanager.NullableEIP2930AccessListFrom(al) + nNull := txmgr.NullableEIP2930AccessList{} + nValid := txmgr.NullableEIP2930AccessListFrom(al) t.Run("MarshalJSON", func(t *testing.T) { _, err := json.Marshal(nNull) @@ -376,7 +376,7 @@ func Test_NullableEIP2930AccessList(t *testing.T) { }) t.Run("UnmarshalJSON", func(t *testing.T) { - var n bulletprooftxmanager.NullableEIP2930AccessList + var n txmgr.NullableEIP2930AccessList err := json.Unmarshal(nil, &n) require.EqualError(t, err, "unexpected end of JSON input") @@ -402,7 +402,7 @@ func Test_NullableEIP2930AccessList(t *testing.T) { }) t.Run("Scan", func(t *testing.T) { - n := new(bulletprooftxmanager.NullableEIP2930AccessList) + n := new(txmgr.NullableEIP2930AccessList) err := n.Scan(nil) require.NoError(t, err) assert.False(t, n.Valid) diff --git a/core/chains/evm/types/types.go b/core/chains/evm/types/types.go index cc323103b74..91c25d5c090 100644 --- a/core/chains/evm/types/types.go +++ b/core/chains/evm/types/types.go @@ -10,10 +10,10 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" + "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -42,10 +42,10 @@ type ORM interface { CreateNode(data NewNode) (Node, error) DeleteNode(id int64) error GetChainsByIDs(ids []utils.Big) (chains []Chain, err error) - GetNodesByChainIDs(chainIDs []utils.Big) (nodes []Node, err error) - Node(id int32) (Node, error) - Nodes(offset, limit int) ([]Node, int, error) - NodesForChain(chainID utils.Big, offset, limit int) ([]Node, int, error) + GetNodesByChainIDs(chainIDs []utils.Big, qopts ...pg.QOpt) (nodes []Node, err error) + Node(id int32, qopts ...pg.QOpt) (Node, error) + Nodes(offset, limit int, qopts ...pg.QOpt) ([]Node, int, error) + NodesForChain(chainID utils.Big, offset, limit int, qopts ...pg.QOpt) ([]Node, int, error) ChainConfigORM } @@ -82,6 +82,7 @@ type ChainCfg struct { MinRequiredOutgoingConfirmations null.Int MinimumContractPayment *assets.Link OCRObservationTimeout *models.Duration + NodeNoNewHeadsThreshold *models.Duration } func (c *ChainCfg) Scan(value interface{}) error { @@ -115,6 +116,9 @@ type Node struct { SendOnly bool CreatedAt time.Time UpdatedAt time.Time + // State doesn't exist in the DB, it's used to hold an in-memory state for + // rendering + State string `db:"-"` } // Receipt represents an ethereum receipt. diff --git a/core/chains/terra/chain.go b/core/chains/terra/chain.go index e3bda4071dd..93bdb25dae0 100644 --- a/core/chains/terra/chain.go +++ b/core/chains/terra/chain.go @@ -1,6 +1,7 @@ package terra import ( + "context" "fmt" "math" "math/rand" @@ -16,9 +17,11 @@ import ( "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/chains/terra/monitor" "github.com/smartcontractkit/chainlink/core/chains/terra/terratxm" "github.com/smartcontractkit/chainlink/core/chains/terra/types" "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" @@ -31,18 +34,19 @@ import ( // So we set a fairly high timeout here. const DefaultRequestTimeout = 30 * time.Second -//go:generate mockery --name MsgEnqueuer --srcpkg github.com/smartcontractkit/chainlink-terra/pkg/terra --output ./mocks/ --case=underscore +//go:generate mockery --name TxManager --srcpkg github.com/smartcontractkit/chainlink-terra/pkg/terra --output ./mocks/ --case=underscore //go:generate mockery --name Reader --srcpkg github.com/smartcontractkit/chainlink-terra/pkg/terra/client --output ./mocks/ --case=underscore //go:generate mockery --name Chain --srcpkg github.com/smartcontractkit/chainlink-terra/pkg/terra --output ./mocks/ --case=underscore var _ terra.Chain = (*chain)(nil) type chain struct { utils.StartStopOnce - id string - cfg terra.Config - txm *terratxm.Txm - orm types.ORM - lggr logger.Logger + id string + cfg terra.Config + txm *terratxm.Txm + balanceMonitor services.ServiceCtx + orm types.ORM + lggr logger.Logger } // NewChain returns a new chain backed by node. @@ -67,7 +71,8 @@ func NewChain(db *sqlx.DB, ks keystore.Terra, logCfg pg.LogConfig, eb pg.EventBr }, nil }), }, lggr) - ch.txm = terratxm.NewTxm(db, tc, *gpe, dbchain.ID, cfg, ks, lggr, logCfg, eb) + ch.txm = terratxm.NewTxm(db, tc, *gpe, ch.id, cfg, ks, lggr, logCfg, eb) + ch.balanceMonitor = monitor.NewBalanceMonitor(ch.id, cfg, lggr, ks, ch.Reader) return &ch, nil } @@ -84,7 +89,7 @@ func (c *chain) UpdateConfig(cfg db.ChainCfg) { c.cfg.Update(cfg) } -func (c *chain) MsgEnqueuer() terra.MsgEnqueuer { +func (c *chain) TxManager() terra.TxManager { return c.txm } @@ -93,7 +98,7 @@ func (c *chain) Reader(name string) (terraclient.Reader, error) { } // getClient returns a client, optionally requiring a specific node by name. -func (c *chain) getClient(name string) (*terraclient.Client, error) { +func (c *chain) getClient(name string) (terraclient.ReaderWriter, error) { //TODO cache clients? var node db.Node if name == "" { // Any node @@ -120,17 +125,21 @@ func (c *chain) getClient(name string) (*terraclient.Client, error) { if err != nil { return nil, errors.Wrap(err, "failed to create client") } - c.lggr.Debugw("Created client", "name", name, "tendermint-url", node.TendermintURL) + c.lggr.Debugw("Created client", "name", node.Name, "tendermint-url", node.TendermintURL) return client, nil } -func (c *chain) Start() error { +// Start starts terra chain. +func (c *chain) Start(ctx context.Context) error { return c.StartOnce("Chain", func() error { c.lggr.Debug("Starting") //TODO dial client? c.lggr.Debug("Starting txm") - return c.txm.Start() + c.lggr.Debug("Starting balance monitor") + return multierr.Combine( + c.txm.Start(ctx), + c.balanceMonitor.Start(ctx)) }) } @@ -138,7 +147,9 @@ func (c *chain) Close() error { return c.StopOnce("Chain", func() error { c.lggr.Debug("Stopping") c.lggr.Debug("Stopping txm") - return c.txm.Close() + c.lggr.Debug("Stopping balance monitor") + return multierr.Combine(c.txm.Close(), + c.balanceMonitor.Close()) }) } diff --git a/core/chains/terra/chain_set.go b/core/chains/terra/chain_set.go index 1c80fbdd61f..487494f3403 100644 --- a/core/chains/terra/chain_set.go +++ b/core/chains/terra/chain_set.go @@ -1,6 +1,8 @@ package terra import ( + "context" + "database/sql" "fmt" "sync" @@ -19,6 +21,13 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) +var ( + // ErrChainIDEmpty is returned when chain is required but was empty. + ErrChainIDEmpty = errors.New("chain id empty") + // ErrChainIDInvalid is returned when a chain id does not match any configured chains. + ErrChainIDInvalid = errors.New("chain id does not match any local chains") +) + // ChainSetOpts holds options for configuring a ChainSet. type ChainSetOpts struct { Config coreconfig.GeneralConfig @@ -65,9 +74,9 @@ func (o ChainSetOpts) newChain(dbchain db.Chain) (*chain, error) { type ChainSet interface { terra.ChainSet - Add(string, db.ChainCfg) (db.Chain, error) + Add(context.Context, string, db.ChainCfg) (db.Chain, error) Remove(string) error - Configure(id string, enabled bool, config db.ChainCfg) (db.Chain, error) + Configure(ctx context.Context, id string, enabled bool, config db.ChainCfg) (db.Chain, error) ORM() types.ORM } @@ -112,7 +121,10 @@ func (c *chainSet) ORM() types.ORM { return c.opts.ORM } -func (c *chainSet) Chain(id string) (terra.Chain, error) { +func (c *chainSet) Chain(ctx context.Context, id string) (terra.Chain, error) { + if id == "" { + return nil, ErrChainIDEmpty + } if err := c.StartStopOnce.Ready(); err != nil { return nil, err } @@ -143,17 +155,20 @@ func (c *chainSet) Chain(id string) (terra.Chain, error) { opts := c.opts dbchain, err := opts.ORM.Chain(id) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrChainIDInvalid + } return nil, err } - err = c.initializeChain(&dbchain) + err = c.initializeChain(ctx, &dbchain) if err != nil { return nil, err } return c.chains[id], nil } -func (c *chainSet) Add(id string, config db.ChainCfg) (db.Chain, error) { +func (c *chainSet) Add(ctx context.Context, id string, config db.ChainCfg) (db.Chain, error) { c.chainsMu.Lock() defer c.chainsMu.Unlock() @@ -165,18 +180,18 @@ func (c *chainSet) Add(id string, config db.ChainCfg) (db.Chain, error) { if err != nil { return db.Chain{}, err } - return dbchain, c.initializeChain(&dbchain) + return dbchain, c.initializeChain(ctx, &dbchain) } // Requires a lock on chainsMu -func (c *chainSet) initializeChain(dbchain *db.Chain) error { +func (c *chainSet) initializeChain(ctx context.Context, dbchain *db.Chain) error { // Start it cid := dbchain.ID chain, err := c.opts.newChain(*dbchain) if err != nil { return errors.Wrapf(err, "initializeChain: failed to instantiate chain %s", dbchain.ID) } - if err = chain.Start(); err != nil { + if err = chain.Start(ctx); err != nil { return errors.Wrapf(err, "initializeChain: failed to start chain %s", dbchain.ID) } c.chains[cid] = chain @@ -200,7 +215,7 @@ func (c *chainSet) Remove(id string) error { return chain.Close() } -func (c *chainSet) Configure(id string, enabled bool, config db.ChainCfg) (db.Chain, error) { +func (c *chainSet) Configure(ctx context.Context, id string, enabled bool, config db.ChainCfg) (db.Chain, error) { c.chainsMu.Lock() defer c.chainsMu.Unlock() @@ -219,7 +234,7 @@ func (c *chainSet) Configure(id string, enabled bool, config db.ChainCfg) (db.Ch return db.Chain{}, chain.Close() case !exists && enabled: // Chain was toggled to enabled - return dbchain, c.initializeChain(&dbchain) + return dbchain, c.initializeChain(ctx, &dbchain) case exists: // Exists in memory, no toggling: Update in-memory chain chain.UpdateConfig(config) @@ -228,7 +243,8 @@ func (c *chainSet) Configure(id string, enabled bool, config db.ChainCfg) (db.Ch return dbchain, nil } -func (c *chainSet) Start() error { +// Start starts terra ChainSet. +func (c *chainSet) Start(ctx context.Context) error { //TODO if terra disabled, warn and return? return c.StartOnce("ChainSet", func() error { c.lggr.Debug("Starting") @@ -237,7 +253,7 @@ func (c *chainSet) Start() error { defer c.chainsMu.Unlock() var started int for _, ch := range c.chains { - if err := ch.Start(); err != nil { + if err := ch.Start(ctx); err != nil { c.lggr.Errorw(fmt.Sprintf("Chain with ID %s failed to start. You will need to fix this issue and restart the Chainlink node before any services that use this chain will work properly. Got error: %v", ch.ID(), err), "err", err) continue } diff --git a/core/chains/terra/denom/denom.go b/core/chains/terra/denom/denom.go new file mode 100644 index 00000000000..e0a6d4c2ae8 --- /dev/null +++ b/core/chains/terra/denom/denom.go @@ -0,0 +1,38 @@ +package denom + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func init() { + for _, d := range []struct { + denom string + decimals int64 + }{ + {"luna", 0}, + {"mluna", 3}, + {"uluna", 6}, + } { + dec := sdk.NewDecWithPrec(1, d.decimals) + if err := sdk.RegisterDenom(d.denom, dec); err != nil { + panic(fmt.Errorf("failed to register denomination %q: %w", d.denom, err)) + } + } +} + +// ConvertToLuna is a helper for converting to luna. +func ConvertToLuna(coin sdk.Coin) (sdk.DecCoin, error) { + return sdk.ConvertDecCoin(sdk.NewDecCoinFromCoin(coin), "luna") +} + +// ConvertToULuna is a helper for converting to uluna. +func ConvertToULuna(coin sdk.DecCoin) (sdk.Coin, error) { + decCoin, err := sdk.ConvertDecCoin(coin, "uluna") + if err != nil { + return sdk.Coin{}, err + } + truncated, _ := decCoin.TruncateDecimal() + return truncated, nil +} diff --git a/core/chains/terra/denom/denom_test.go b/core/chains/terra/denom/denom_test.go new file mode 100644 index 00000000000..ee1284f6300 --- /dev/null +++ b/core/chains/terra/denom/denom_test.go @@ -0,0 +1,29 @@ +package denom + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestConvertToLuna(t *testing.T) { + tests := []struct { + coin types.Coin + exp string + }{ + {types.NewInt64Coin("uluna", 1), "0.000001000000000000luna"}, + {types.NewInt64Coin("uluna", 0), "0.000000000000000000luna"}, + {types.NewInt64Coin("luna", 1), "1.000000000000000000luna"}, + {types.NewInt64Coin("uluna", 1000000), "1.000000000000000000luna"}, + {types.NewInt64Coin("mluna", 1000000), "1000.000000000000000000luna"}, + {types.NewInt64Coin("mluna", 123456789), "123456.789000000000000000luna"}, + } + for _, tt := range tests { + t.Run(tt.coin.String(), func(t *testing.T) { + got, err := ConvertToLuna(tt.coin) + require.NoError(t, err) + require.Equal(t, tt.exp, got.String()) + }) + } +} diff --git a/core/chains/terra/mocks/chain.go b/core/chains/terra/mocks/chain.go index dcda7b7dffe..56cf8ffd742 100644 --- a/core/chains/terra/mocks/chain.go +++ b/core/chains/terra/mocks/chain.go @@ -3,7 +3,10 @@ package mocks import ( + context "context" + client "github.com/smartcontractkit/chainlink-terra/pkg/terra/client" + mock "github.com/stretchr/testify/mock" terra "github.com/smartcontractkit/chainlink-terra/pkg/terra" @@ -72,22 +75,6 @@ func (_m *Chain) ID() string { return r0 } -// MsgEnqueuer provides a mock function with given fields: -func (_m *Chain) MsgEnqueuer() terra.MsgEnqueuer { - ret := _m.Called() - - var r0 terra.MsgEnqueuer - if rf, ok := ret.Get(0).(func() terra.MsgEnqueuer); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(terra.MsgEnqueuer) - } - } - - return r0 -} - // Reader provides a mock function with given fields: nodeName func (_m *Chain) Reader(nodeName string) (client.Reader, error) { ret := _m.Called(nodeName) @@ -125,16 +112,32 @@ func (_m *Chain) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *Chain) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Chain) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } return r0 } + +// TxManager provides a mock function with given fields: +func (_m *Chain) TxManager() terra.TxManager { + ret := _m.Called() + + var r0 terra.TxManager + if rf, ok := ret.Get(0).(func() terra.TxManager); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(terra.TxManager) + } + } + + return r0 +} diff --git a/core/chains/terra/mocks/chain_set.go b/core/chains/terra/mocks/chain_set.go index e84787d6e27..261444e8e6f 100644 --- a/core/chains/terra/mocks/chain_set.go +++ b/core/chains/terra/mocks/chain_set.go @@ -3,6 +3,8 @@ package mocks import ( + context "context" + terra "github.com/smartcontractkit/chainlink-terra/pkg/terra" mock "github.com/stretchr/testify/mock" ) @@ -12,13 +14,13 @@ type ChainSet struct { mock.Mock } -// Chain provides a mock function with given fields: id -func (_m *ChainSet) Chain(id string) (terra.Chain, error) { - ret := _m.Called(id) +// Chain provides a mock function with given fields: ctx, id +func (_m *ChainSet) Chain(ctx context.Context, id string) (terra.Chain, error) { + ret := _m.Called(ctx, id) var r0 terra.Chain - if rf, ok := ret.Get(0).(func(string) terra.Chain); ok { - r0 = rf(id) + if rf, ok := ret.Get(0).(func(context.Context, string) terra.Chain); ok { + r0 = rf(ctx, id) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(terra.Chain) @@ -26,8 +28,8 @@ func (_m *ChainSet) Chain(id string) (terra.Chain, error) { } var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(id) + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) } else { r1 = ret.Error(1) } @@ -77,13 +79,13 @@ func (_m *ChainSet) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *ChainSet) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *ChainSet) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/chains/terra/mocks/msg_enqueuer.go b/core/chains/terra/mocks/msg_enqueuer.go deleted file mode 100644 index c522c253abe..00000000000 --- a/core/chains/terra/mocks/msg_enqueuer.go +++ /dev/null @@ -1,59 +0,0 @@ -// Code generated by mockery v2.8.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// MsgEnqueuer is an autogenerated mock type for the MsgEnqueuer type -type MsgEnqueuer struct { - mock.Mock -} - -// Close provides a mock function with given fields: -func (_m *MsgEnqueuer) Close() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Enqueue provides a mock function with given fields: contractID, msg -func (_m *MsgEnqueuer) Enqueue(contractID string, msg []byte) (int64, error) { - ret := _m.Called(contractID, msg) - - var r0 int64 - if rf, ok := ret.Get(0).(func(string, []byte) int64); ok { - r0 = rf(contractID, msg) - } else { - r0 = ret.Get(0).(int64) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, []byte) error); ok { - r1 = rf(contractID, msg) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Start provides a mock function with given fields: -func (_m *MsgEnqueuer) Start() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/core/chains/terra/mocks/tx_manager.go b/core/chains/terra/mocks/tx_manager.go new file mode 100644 index 00000000000..a92fa73fadb --- /dev/null +++ b/core/chains/terra/mocks/tx_manager.go @@ -0,0 +1,85 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/cosmos/cosmos-sdk/types" + terra "github.com/smartcontractkit/chainlink-terra/pkg/terra" + mock "github.com/stretchr/testify/mock" +) + +// TxManager is an autogenerated mock type for the TxManager type +type TxManager struct { + mock.Mock +} + +// Enqueue provides a mock function with given fields: contractID, msg +func (_m *TxManager) Enqueue(contractID string, msg types.Msg) (int64, error) { + ret := _m.Called(contractID, msg) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string, types.Msg) int64); ok { + r0 = rf(contractID, msg) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, types.Msg) error); ok { + r1 = rf(contractID, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GasPrice provides a mock function with given fields: +func (_m *TxManager) GasPrice() (types.DecCoin, error) { + ret := _m.Called() + + var r0 types.DecCoin + if rf, ok := ret.Get(0).(func() types.DecCoin); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.DecCoin) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMsgs provides a mock function with given fields: ids +func (_m *TxManager) GetMsgs(ids ...int64) (terra.Msgs, error) { + _va := make([]interface{}, len(ids)) + for _i := range ids { + _va[_i] = ids[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 terra.Msgs + if rf, ok := ret.Get(0).(func(...int64) terra.Msgs); ok { + r0 = rf(ids...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(terra.Msgs) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(...int64) error); ok { + r1 = rf(ids...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/chains/terra/monitor/balance.go b/core/chains/terra/monitor/balance.go new file mode 100644 index 00000000000..da30f51d3ff --- /dev/null +++ b/core/chains/terra/monitor/balance.go @@ -0,0 +1,144 @@ +package monitor + +import ( + "context" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/smartcontractkit/chainlink-terra/pkg/terra/client" + + "github.com/smartcontractkit/chainlink/core/chains/terra/denom" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/terrakey" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// Config defines the monitor configuration. +type Config interface { + BlockRate() time.Duration +} + +// Keystore provides the keys to be monitored. +type Keystore interface { + GetAll() ([]terrakey.Key, error) +} + +// NewBalanceMonitor returns a balance monitoring services.ServiceCtx which reports the luna balance of all ks keys to prometheus. +func NewBalanceMonitor(chainID string, cfg Config, lggr logger.Logger, ks Keystore, newReader func(string) (client.Reader, error)) services.ServiceCtx { + return newBalanceMonitor(chainID, cfg, lggr, ks, newReader) +} + +func newBalanceMonitor(chainID string, cfg Config, lggr logger.Logger, ks Keystore, newReader func(string) (client.Reader, error)) *balanceMonitor { + b := balanceMonitor{ + chainID: chainID, + cfg: cfg, + lggr: lggr.Named("BalanceMonitor"), + ks: ks, + newReader: newReader, + stop: make(chan struct{}), + done: make(chan struct{}), + } + b.updateFn = b.updateProm + return &b +} + +type balanceMonitor struct { + utils.StartStopOnce + chainID string + cfg Config + lggr logger.Logger + ks Keystore + newReader func(string) (client.Reader, error) + updateFn func(acc sdk.AccAddress, bal *sdk.DecCoin) // overridable for testing + + reader client.Reader + + stop, done chan struct{} +} + +// Start starts balance monitor for terra. +func (b *balanceMonitor) Start(context.Context) error { + return b.StartOnce("TerraBalanceMonitor", func() error { + go b.monitor() + return nil + }) +} + +func (b *balanceMonitor) Close() error { + return b.StopOnce("TerraBalanceMonitor", func() error { + close(b.stop) + <-b.done + return nil + }) +} + +func (b *balanceMonitor) monitor() { + defer close(b.done) + + tick := time.After(utils.WithJitter(b.cfg.BlockRate())) + for { + select { + case <-b.stop: + return + case <-tick: + b.updateBalances() + tick = time.After(utils.WithJitter(b.cfg.BlockRate())) + } + } +} + +// getReader returns the cached client.Reader, or creates a new one if nil. +func (b *balanceMonitor) getReader() (client.Reader, error) { + if b.reader == nil { + var err error + b.reader, err = b.newReader("") + if err != nil { + return nil, err + } + } + return b.reader, nil +} + +func (b *balanceMonitor) updateBalances() { + keys, err := b.ks.GetAll() + if err != nil { + b.lggr.Errorw("Failed to get keys", "err", err) + return + } + if len(keys) == 0 { + return + } + reader, err := b.getReader() + if err != nil { + b.lggr.Errorw("Failed to get client", "err", err) + return + } + var gotSomeBals bool + for _, k := range keys { + // Check for shutdown signal, since Balance blocks and may be slow. + select { + case <-b.stop: + return + default: + } + acc := sdk.AccAddress(k.PublicKey().Address()) + bal, err := reader.Balance(acc, "uluna") + if err != nil { + b.lggr.Errorw("Failed to get balance", "account", acc, "err", err) + continue + } + gotSomeBals = true + balLuna, err := denom.ConvertToLuna(*bal) + if err != nil { + b.lggr.Errorw("Failed to convert uluna to luna", "account", acc, "err", err) + continue + } + b.updateFn(acc, &balLuna) + } + if !gotSomeBals { + // Try a new client next time. + b.reader = nil + } +} diff --git a/core/chains/terra/monitor/balance_test.go b/core/chains/terra/monitor/balance_test.go new file mode 100644 index 00000000000..8fcfdbd612b --- /dev/null +++ b/core/chains/terra/monitor/balance_test.go @@ -0,0 +1,82 @@ +package monitor + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-terra/pkg/terra/client/mocks" + + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/terrakey" +) + +func TestBalanceMonitor(t *testing.T) { + const chainID = "Chainlinktest-42" + ks := keystore{terrakey.New(), terrakey.New(), terrakey.New()} + bals := []sdk.Coin{ + sdk.NewInt64Coin("uluna", 0), + sdk.NewInt64Coin("uluna", 1), + sdk.NewInt64Coin("uluna", 100000000000), + } + expBals := []string{ + "0.000000000000000000luna", + "0.000001000000000000luna", + "100000.000000000000000000luna", + } + client := new(mocks.ReaderWriter) + type update struct{ acc, bal string } + var exp []update + for i := range bals { + acc := sdk.AccAddress(ks[i].PublicKey().Address()) + client.On("Balance", acc, bals[i].Denom).Return(&bals[i], nil) + exp = append(exp, update{acc.String(), expBals[i]}) + } + cfg := &config{blockRate: time.Second} + b := newBalanceMonitor(chainID, cfg, logger.TestLogger(t), ks, nil) + var got []update + done := make(chan struct{}) + b.updateFn = func(acc sdk.AccAddress, bal *sdk.DecCoin) { + select { + case <-done: + return + default: + } + got = append(got, update{acc.String(), bal.String()}) + if len(got) == len(exp) { + close(done) + } + } + b.reader = client + + require.NoError(t, b.Start(testutils.Context(t))) + t.Cleanup(func() { + assert.NoError(t, b.Close()) + client.AssertExpectations(t) + }) + select { + case <-time.After(testutils.WaitTimeout(t)): + t.Fatal("timed out waiting for balance monitor") + case <-done: + } + + assert.EqualValues(t, exp, got) +} + +type config struct { + blockRate time.Duration +} + +func (c *config) BlockRate() time.Duration { + return c.blockRate +} + +type keystore []terrakey.Key + +func (k keystore) GetAll() ([]terrakey.Key, error) { + return k, nil +} diff --git a/core/chains/terra/monitor/prom.go b/core/chains/terra/monitor/prom.go new file mode 100644 index 00000000000..2e68e77ac63 --- /dev/null +++ b/core/chains/terra/monitor/prom.go @@ -0,0 +1,21 @@ +package monitor + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var promTerraBalance = promauto.NewGaugeVec( + prometheus.GaugeOpts{Name: "terra_balance", Help: "Terra account balances"}, + []string{"account", "terraChainID", "denomination"}, +) + +func (b *balanceMonitor) updateProm(acc sdk.AccAddress, bal *sdk.DecCoin) { + balF, err := bal.Amount.Float64() + if err != nil { + b.lggr.Errorw("Failed to convert balance to float", "err", err) + return + } + promTerraBalance.WithLabelValues(acc.String(), b.chainID, bal.GetDenom()).Set(balF) +} diff --git a/core/chains/terra/orm_test.go b/core/chains/terra/orm_test.go index 61d5205b6a3..14bab128c6d 100644 --- a/core/chains/terra/orm_test.go +++ b/core/chains/terra/orm_test.go @@ -103,6 +103,9 @@ func Test_ORM(t *testing.T) { gotNamed, err := orm.NodeNamed("third") require.NoError(t, err) assertEqual(t, newNode3, gotNamed) + + assert.NoError(t, orm.DeleteChain(chainIDA)) + assert.NoError(t, orm.DeleteChain(chainIDB)) } func assertEqual(t *testing.T, newNode types.NewNode, gotNode db.Node) { diff --git a/core/chains/terra/terratxm/orm.go b/core/chains/terra/terratxm/orm.go index 76f3ae658e8..7bbc45c09a7 100644 --- a/core/chains/terra/terratxm/orm.go +++ b/core/chains/terra/terratxm/orm.go @@ -30,29 +30,30 @@ func NewORM(chainID string, db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig) * } // InsertMsg inserts a terra msg, assumed to be a serialized terra ExecuteContractMsg. -func (o *ORM) InsertMsg(contractID string, msg []byte) (int64, error) { +func (o *ORM) InsertMsg(contractID, typeURL string, msg []byte) (int64, error) { var tm terra.Msg - err := o.q.Get(&tm, `INSERT INTO terra_msgs (contract_id, raw, state, terra_chain_id, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *`, contractID, msg, db.Unstarted, o.chainID) + err := o.q.Get(&tm, `INSERT INTO terra_msgs (contract_id, type, raw, state, terra_chain_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) RETURNING *`, contractID, typeURL, msg, db.Unstarted, o.chainID) if err != nil { return 0, err } return tm.ID, nil } -// SelectMsgsWithState selects the oldest messages with a given state up to limit. -func (o *ORM) SelectMsgsWithState(state db.State, limit int64) (terra.Msgs, error) { +// GetMsgsState returns the oldest messages with a given state up to limit. +func (o *ORM) GetMsgsState(state db.State, limit int64, qopts ...pg.QOpt) (terra.Msgs, error) { if limit < 1 { return terra.Msgs{}, errors.New("limit must be greater than 0") } + q := o.q.WithOpts(qopts...) var msgs terra.Msgs - if err := o.q.Select(&msgs, `SELECT * FROM terra_msgs WHERE state = $1 AND terra_chain_id = $2 ORDER BY created_at LIMIT $3`, state, o.chainID, limit); err != nil { + if err := q.Select(&msgs, `SELECT * FROM terra_msgs WHERE state = $1 AND terra_chain_id = $2 ORDER BY created_at LIMIT $3`, state, o.chainID, limit); err != nil { return nil, err } return msgs, nil } -// SelectMsgsWithIDs selects messages the given ids -func (o *ORM) SelectMsgsWithIDs(ids []int64) (terra.Msgs, error) { +// GetMsgs returns any messages matching ids. +func (o *ORM) GetMsgs(ids ...int64) (terra.Msgs, error) { var msgs terra.Msgs if err := o.q.Select(&msgs, `SELECT * FROM terra_msgs WHERE id = ANY($1)`, ids); err != nil { return nil, err @@ -60,9 +61,9 @@ func (o *ORM) SelectMsgsWithIDs(ids []int64) (terra.Msgs, error) { return msgs, nil } -// UpdateMsgsWithState update the msgs with the given ids to the given state +// UpdateMsgs updates msgs with the given ids. // Note state transitions are validated at the db level. -func (o *ORM) UpdateMsgsWithState(ids []int64, state db.State, txHash *string, qopts ...pg.QOpt) error { +func (o *ORM) UpdateMsgs(ids []int64, state db.State, txHash *string, qopts ...pg.QOpt) error { if state == db.Broadcasted && txHash == nil { return errors.New("txHash is required when updating to broadcasted") } diff --git a/core/chains/terra/terratxm/orm_test.go b/core/chains/terra/terratxm/orm_test.go index dea58e62038..3b5d10da4dd 100644 --- a/core/chains/terra/terratxm/orm_test.go +++ b/core/chains/terra/terratxm/orm_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" . "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" + . "github.com/smartcontractkit/chainlink/core/chains/terra/terratxm" ) @@ -26,12 +27,12 @@ func TestORM(t *testing.T) { o := NewORM(chainID, db, lggr, logCfg) // Create - mid, err := o.InsertMsg("0x123", []byte("hello")) + mid, err := o.InsertMsg("0x123", "", []byte("hello")) require.NoError(t, err) assert.NotEqual(t, 0, int(mid)) // Read - unstarted, err := o.SelectMsgsWithState(Unstarted, 5) + unstarted, err := o.GetMsgsState(Unstarted, 5) require.NoError(t, err) require.Equal(t, 1, len(unstarted)) assert.Equal(t, "hello", string(unstarted[0].Raw)) @@ -39,21 +40,21 @@ func TestORM(t *testing.T) { t.Log(unstarted[0].UpdatedAt, unstarted[0].CreatedAt) // Limit - unstarted, err = o.SelectMsgsWithState(Unstarted, 0) + unstarted, err = o.GetMsgsState(Unstarted, 0) assert.Error(t, err) assert.Empty(t, unstarted) - unstarted, err = o.SelectMsgsWithState(Unstarted, -1) + unstarted, err = o.GetMsgsState(Unstarted, -1) assert.Error(t, err) assert.Empty(t, unstarted) - mid2, err := o.InsertMsg("0xabc", []byte("test")) + mid2, err := o.InsertMsg("0xabc", "", []byte("test")) require.NoError(t, err) assert.NotEqual(t, 0, int(mid2)) - unstarted, err = o.SelectMsgsWithState(Unstarted, 1) + unstarted, err = o.GetMsgsState(Unstarted, 1) require.NoError(t, err) require.Equal(t, 1, len(unstarted)) assert.Equal(t, "hello", string(unstarted[0].Raw)) assert.Equal(t, chainID, unstarted[0].ChainID) - unstarted, err = o.SelectMsgsWithState(Unstarted, 2) + unstarted, err = o.GetMsgsState(Unstarted, 2) require.NoError(t, err) require.Equal(t, 2, len(unstarted)) assert.Equal(t, "test", string(unstarted[1].Raw)) @@ -61,9 +62,9 @@ func TestORM(t *testing.T) { // Update txHash := "123" - err = o.UpdateMsgsWithState([]int64{mid}, Broadcasted, &txHash) + err = o.UpdateMsgs([]int64{mid}, Broadcasted, &txHash) require.NoError(t, err) - broadcasted, err := o.SelectMsgsWithState(Broadcasted, 5) + broadcasted, err := o.GetMsgsState(Broadcasted, 5) require.NoError(t, err) require.Equal(t, 1, len(broadcasted)) assert.Equal(t, broadcasted[0].Raw, unstarted[0].Raw) @@ -71,9 +72,9 @@ func TestORM(t *testing.T) { assert.Equal(t, *broadcasted[0].TxHash, txHash) assert.Equal(t, chainID, broadcasted[0].ChainID) - err = o.UpdateMsgsWithState([]int64{mid}, Confirmed, nil) + err = o.UpdateMsgs([]int64{mid}, Confirmed, nil) require.NoError(t, err) - confirmed, err := o.SelectMsgsWithState(Confirmed, 5) + confirmed, err := o.GetMsgsState(Confirmed, 5) require.NoError(t, err) require.Equal(t, 1, len(confirmed)) } diff --git a/core/chains/terra/terratxm/txm.go b/core/chains/terra/terratxm/txm.go index 150076981b5..f6f5e1aba15 100644 --- a/core/chains/terra/terratxm/txm.go +++ b/core/chains/terra/terratxm/txm.go @@ -6,16 +6,19 @@ import ( "strings" "time" + "github.com/gogo/protobuf/proto" + "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" + sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/pkg/errors" + "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/tendermint/tendermint/crypto/tmhash" wasmtypes "github.com/terra-money/core/x/wasm/types" "github.com/smartcontractkit/chainlink-terra/pkg/terra" terraclient "github.com/smartcontractkit/chainlink-terra/pkg/terra/client" "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -25,7 +28,10 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -var _ services.Service = (*Txm)(nil) +var ( + _ services.ServiceCtx = (*Txm)(nil) + _ terra.TxManager = (*Txm)(nil) +) // Txm manages transactions for the terra blockchain. type Txm struct { @@ -59,7 +65,7 @@ func NewTxm(db *sqlx.DB, tc func() (terraclient.ReaderWriter, error), gpe terrac } // Start subscribes to pg notifications about terra msg inserts and processes them. -func (txm *Txm) Start() error { +func (txm *Txm) Start(context.Context) error { return txm.starter.StartOnce("terratxm", func() error { sub, err := txm.eb.Subscribe(pg.ChannelInsertOnTerraMsg, "") if err != nil { @@ -75,7 +81,7 @@ func (txm *Txm) confirmAnyUnconfirmed(ctx context.Context) { // Confirm any broadcasted but not confirmed txes. // This is an edge case if we crash after having broadcasted but before we confirm. for { - broadcasted, err := txm.orm.SelectMsgsWithState(db.Broadcasted, txm.cfg.MaxMsgsPerBatch()) + broadcasted, err := txm.orm.GetMsgsState(db.Broadcasted, txm.cfg.MaxMsgsPerBatch()) if err != nil { // Should never happen but if so, theoretically can retry with a reboot txm.lggr.Criticalw("unable to look for broadcasted but unconfirmed txes", "err", err) @@ -126,41 +132,85 @@ func (txm *Txm) run() { } } +var ( + typeMsgSend = sdk.MsgTypeURL(&types.MsgSend{}) + typeMsgExecuteContract = sdk.MsgTypeURL(&wasmtypes.MsgExecuteContract{}) +) + +func unmarshalMsg(msgType string, raw []byte) (sdk.Msg, string, error) { + switch msgType { + case typeMsgSend: + var ms types.MsgSend + err := ms.Unmarshal(raw) + if err != nil { + return nil, "", err + } + return &ms, ms.FromAddress, nil + case typeMsgExecuteContract: + var ms wasmtypes.MsgExecuteContract + err := ms.Unmarshal(raw) + if err != nil { + return nil, "", err + } + return &ms, ms.Sender, nil + } + return nil, "", errors.Errorf("unrecognized message type: %s", msgType) +} + func (txm *Txm) sendMsgBatch(ctx context.Context) { - unstarted, err := txm.orm.SelectMsgsWithState(db.Unstarted, txm.cfg.MaxMsgsPerBatch()) + var notExpired, expired terra.Msgs + err := txm.orm.q.Transaction(func(tx pg.Queryer) error { + unstarted, err := txm.orm.GetMsgsState(db.Unstarted, txm.cfg.MaxMsgsPerBatch(), pg.WithQueryer(tx)) + if err != nil { + txm.lggr.Errorw("unable to read unstarted msgs", "err", err) + return err + } + cutoff := time.Now().Add(-txm.cfg.TxMsgTimeout()) + for _, msg := range unstarted { + if msg.CreatedAt.Before(cutoff) { + expired = append(expired, msg) + } else { + notExpired = append(notExpired, msg) + } + } + err = txm.orm.UpdateMsgs(expired.GetIDs(), db.Errored, nil, pg.WithQueryer(tx)) + if err != nil { + // Assume transient db error retry + txm.lggr.Errorw("unable to mark expired txes as errored", "err", err) + return err + } + return nil + }) if err != nil { - txm.lggr.Errorw("unable to read unstarted msgs", "err", err) return } - if len(unstarted) == 0 { + if len(notExpired) == 0 { return } - txm.lggr.Debugw("building a batch", "batch", unstarted) + txm.lggr.Debugw("building a batch", "not expired", notExpired, "marked expired", expired) var msgsByFrom = make(map[string]terra.Msgs) - for _, m := range unstarted { - var ms wasmtypes.MsgExecuteContract - err := ms.Unmarshal(m.Raw) - if err != nil { + for _, m := range notExpired { + msg, sender, err2 := unmarshalMsg(m.Type, m.Raw) + if err2 != nil { // Should be impossible given the check in Enqueue - txm.lggr.Criticalw("failed to unmarshal msg, skipping", "err", err, "msg", m) + txm.lggr.Criticalw("Failed to unmarshal msg, skipping", "err", err2, "msg", m) continue } - m.ExecuteContract = &ms - _, err = sdk.AccAddressFromBech32(ms.Sender) - if err != nil { + m.DecodedMsg = msg + _, err2 = sdk.AccAddressFromBech32(sender) + if err2 != nil { // Should never happen, we parse sender on Enqueue - txm.lggr.Criticalw("unable to parse sender", "err", err, "sender", ms.Sender) + txm.lggr.Criticalw("Unable to parse sender", "err", err2, "sender", sender) continue } - msgsByFrom[ms.Sender] = append(msgsByFrom[ms.Sender], m) + msgsByFrom[sender] = append(msgsByFrom[sender], m) } txm.lggr.Debugw("msgsByFrom", "msgsByFrom", msgsByFrom) - prices := txm.gpe.GasPrices() - gasPrice, ok := prices["uluna"] - if !ok { + gasPrice, err := txm.GasPrice() + if err != nil { // Should be impossible - txm.lggr.Criticalw("unexpected empty uluna price") + txm.lggr.Criticalw("Failed to get gas price", "err", err) return } for s, msgs := range msgsByFrom { @@ -205,7 +255,7 @@ func (txm *Txm) sendMsgBatchFromAddress(ctx context.Context, gasPrice sdk.DecCoi return } txm.lggr.Debugw("simulation results", "from", sender, "succeeded", simResults.Succeeded, "failed", simResults.Failed) - err = txm.orm.UpdateMsgsWithState(simResults.Failed.GetSimMsgsIDs(), db.Errored, nil) + err = txm.orm.UpdateMsgs(simResults.Failed.GetSimMsgsIDs(), db.Errored, nil) if err != nil { txm.lggr.Errorw("unable to mark failed sim txes as errored", "err", err, "from", sender.String()) // If we can't mark them as failed retry on next poll. Presumably same ones will fail. @@ -246,7 +296,7 @@ func (txm *Txm) sendMsgBatchFromAddress(ctx context.Context, gasPrice sdk.DecCoi var resp *txtypes.BroadcastTxResponse err = txm.orm.q.Transaction(func(tx pg.Queryer) error { txHash := strings.ToUpper(hex.EncodeToString(tmhash.Sum(signedTx))) - err = txm.orm.UpdateMsgsWithState(simResults.Succeeded.GetSimMsgsIDs(), db.Broadcasted, &txHash, pg.WithQueryer(tx)) + err = txm.orm.UpdateMsgs(simResults.Succeeded.GetSimMsgsIDs(), db.Broadcasted, &txHash, pg.WithQueryer(tx)) if err != nil { return err } @@ -327,7 +377,7 @@ func (txm *Txm) confirmTx(ctx context.Context, tc terraclient.Reader, txHash str txm.lggr.Infow("successfully sent batch", "hash", txHash, "msgs", broadcasted) // If confirmed mark these as completed. - err = txm.orm.UpdateMsgsWithState(broadcasted, db.Confirmed, nil) + err = txm.orm.UpdateMsgs(broadcasted, db.Confirmed, nil) if err != nil { return err } @@ -336,7 +386,7 @@ func (txm *Txm) confirmTx(ctx context.Context, tc terraclient.Reader, txHash str txm.lggr.Errorw("unable to confirm tx after timeout period, marking errored", "hash", txHash) // If we are unable to confirm the tx after the timeout period // mark these msgs as errored - err := txm.orm.UpdateMsgsWithState(broadcasted, db.Errored, nil) + err := txm.orm.UpdateMsgs(broadcasted, db.Errored, nil) if err != nil { txm.lggr.Errorw("unable to mark timed out txes as errored", "err", err, "txes", broadcasted, "num", len(broadcasted)) return err @@ -345,25 +395,52 @@ func (txm *Txm) confirmTx(ctx context.Context, tc terraclient.Reader, txHash str } // Enqueue enqueue a msg destined for the terra chain. -func (txm *Txm) Enqueue(contractID string, msg []byte) (int64, error) { - // Double check this is an unmarshalable execute contract message. - // Add more supported message types as needed. - var ms wasmtypes.MsgExecuteContract - err := ms.Unmarshal(msg) - if err != nil { - txm.lggr.Errorw("failed to unmarshal msg, skipping", "err", err, "msg", hex.EncodeToString(msg)) - return 0, err +func (txm *Txm) Enqueue(contractID string, msg sdk.Msg) (int64, error) { + switch ms := msg.(type) { + case *wasmtypes.MsgExecuteContract: + _, err := sdk.AccAddressFromBech32(ms.Sender) + if err != nil { + txm.lggr.Errorw("failed to parse sender, skipping", "err", err, "sender", ms.Sender) + return 0, err + } + + case *types.MsgSend: + _, err := sdk.AccAddressFromBech32(ms.FromAddress) + if err != nil { + txm.lggr.Errorw("failed to parse sender, skipping", "err", err, "sender", ms.FromAddress) + return 0, err + } + + default: + return 0, &terra.ErrMsgUnsupported{Msg: msg} } - _, err = sdk.AccAddressFromBech32(ms.Sender) + typeURL := sdk.MsgTypeURL(msg) + raw, err := proto.Marshal(msg) if err != nil { - txm.lggr.Errorw("failed to parse sender, skipping", "err", err, "sender", ms.Sender) + txm.lggr.Errorw("failed to marshal msg, skipping", "err", err, "msg", msg) return 0, err } + // We could consider simulating here too, but that would // introduce another network call and essentially double // the enqueue time. Enqueue is used in the context of OCRs Transmit // and must be fast, so we do the minimum of a db write. - return txm.orm.InsertMsg(contractID, msg) + return txm.orm.InsertMsg(contractID, typeURL, raw) +} + +// GetMsgs returns any messages matching ids. +func (txm *Txm) GetMsgs(ids ...int64) (terra.Msgs, error) { + return txm.orm.GetMsgs(ids...) +} + +// GasPrice returns the gas price from the estimator in uluna. +func (txm *Txm) GasPrice() (sdk.DecCoin, error) { + prices := txm.gpe.GasPrices() + gasPrice, ok := prices["uluna"] + if !ok { + return sdk.DecCoin{}, errors.New("unexpected empty uluna price") + } + return gasPrice, nil } // Close close service diff --git a/core/chains/terra/terratxm/txm_internal_test.go b/core/chains/terra/terratxm/txm_internal_test.go index d74fb9cfcf2..e0bc13dd2ab 100644 --- a/core/chains/terra/terratxm/txm_internal_test.go +++ b/core/chains/terra/terratxm/txm_internal_test.go @@ -1,6 +1,7 @@ package terratxm import ( + "context" "fmt" "math/rand" "testing" @@ -28,16 +29,14 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils/terratest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore" + "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" . "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" ) -func generateExecuteMsg(t *testing.T, msg []byte, from, to cosmostypes.AccAddress) []byte { - msg1 := wasmtypes.NewMsgExecuteContract(from, to, msg, cosmostypes.Coins{}) - d, err := msg1.Marshal() - require.NoError(t, err) - return d +func generateExecuteMsg(t *testing.T, msg []byte, from, to cosmostypes.AccAddress) cosmostypes.Msg { + return wasmtypes.NewMsgExecuteContract(from, to, msg, cosmostypes.Coins{}) } func TestTxm(t *testing.T) { @@ -101,7 +100,7 @@ func TestTxm(t *testing.T) { txm.sendMsgBatch(testutils.Context(t)) // Should be in completed state - completed, err := txm.orm.SelectMsgsWithIDs([]int64{id1}) + completed, err := txm.orm.GetMsgs(id1) require.NoError(t, err) require.Equal(t, 1, len(completed)) assert.Equal(t, completed[0].State, Confirmed) @@ -163,7 +162,7 @@ func TestTxm(t *testing.T) { txm.sendMsgBatch(testutils.Context(t)) // Should be in completed state - completed, err := txm.orm.SelectMsgsWithIDs([]int64{id1, id2}) + completed, err := txm.orm.GetMsgs(id1, id2) require.NoError(t, err) require.Equal(t, 2, len(completed)) assert.Equal(t, completed[0].State, Confirmed) @@ -180,13 +179,13 @@ func TestTxm(t *testing.T) { cfg := terra.NewConfig(terradb.ChainCfg{}, lggr) tcFn := func() (terraclient.ReaderWriter, error) { return tc, nil } txm := NewTxm(db, tcFn, *gpe, chainID, cfg, ks.Terra(), lggr, pgtest.NewPGCfg(true), nil) - i, err := txm.orm.InsertMsg("blah", []byte{0x01}) + i, err := txm.orm.InsertMsg("blah", "", []byte{0x01}) require.NoError(t, err) txh := "0x123" - require.NoError(t, txm.orm.UpdateMsgsWithState([]int64{i}, Broadcasted, &txh)) + require.NoError(t, txm.orm.UpdateMsgs([]int64{i}, Broadcasted, &txh)) err = txm.confirmTx(testutils.Context(t), tc, txh, []int64{i}, 2, 1*time.Millisecond) require.NoError(t, err) - m, err := txm.orm.SelectMsgsWithIDs([]int64{i}) + m, err := txm.orm.GetMsgs(i) require.NoError(t, err) require.Equal(t, 1, len(m)) assert.Equal(t, Errored, m[0].State) @@ -212,22 +211,22 @@ func TestTxm(t *testing.T) { txm := NewTxm(db, tcFn, *gpe, chainID, cfg, ks.Terra(), lggr, pgtest.NewPGCfg(true), nil) // Insert and broadcast 3 msgs with different txhashes. - id1, err := txm.orm.InsertMsg("blah", []byte{0x01}) + id1, err := txm.orm.InsertMsg("blah", "", []byte{0x01}) require.NoError(t, err) - id2, err := txm.orm.InsertMsg("blah", []byte{0x02}) + id2, err := txm.orm.InsertMsg("blah", "", []byte{0x02}) require.NoError(t, err) - id3, err := txm.orm.InsertMsg("blah", []byte{0x03}) + id3, err := txm.orm.InsertMsg("blah", "", []byte{0x03}) require.NoError(t, err) - err = txm.orm.UpdateMsgsWithState([]int64{id1}, Broadcasted, &txHash1) + err = txm.orm.UpdateMsgs([]int64{id1}, Broadcasted, &txHash1) require.NoError(t, err) - err = txm.orm.UpdateMsgsWithState([]int64{id2}, Broadcasted, &txHash2) + err = txm.orm.UpdateMsgs([]int64{id2}, Broadcasted, &txHash2) require.NoError(t, err) - err = txm.orm.UpdateMsgsWithState([]int64{id3}, Broadcasted, &txHash3) + err = txm.orm.UpdateMsgs([]int64{id3}, Broadcasted, &txHash3) require.NoError(t, err) // Confirm them as in a restart while confirming scenario txm.confirmAnyUnconfirmed(testutils.Context(t)) - msgs, err := txm.orm.SelectMsgsWithIDs([]int64{id1, id2, id3}) + msgs, err := txm.orm.GetMsgs(id1, id2, id3) require.NoError(t, err) require.Equal(t, 3, len(msgs)) assert.Equal(t, Confirmed, msgs[0].State) @@ -235,4 +234,36 @@ func TestTxm(t *testing.T) { assert.Equal(t, Confirmed, msgs[2].State) tc.AssertExpectations(t) }) + + t.Run("expired msgs", func(t *testing.T) { + tc := new(tcmocks.ReaderWriter) + timeout := models.MustMakeDuration(1 * time.Millisecond) + tcFn := func() (terraclient.ReaderWriter, error) { return tc, nil } + cfgShortExpiry := terra.NewConfig(terradb.ChainCfg{ + MaxMsgsPerBatch: null.IntFrom(2), + TxMsgTimeout: &timeout, + }, lggr) + txm := NewTxm(db, tcFn, *gpe, chainID, cfgShortExpiry, ks.Terra(), lggr, pgtest.NewPGCfg(true), nil) + + // Send a single one expired + id1, err := txm.orm.InsertMsg("blah", "", []byte{0x03}) + require.NoError(t, err) + time.Sleep(1 * time.Millisecond) + txm.sendMsgBatch(context.Background()) + // Should be marked errored + m, err := txm.orm.GetMsgs(id1) + require.NoError(t, err) + assert.Equal(t, terradb.Errored, m[0].State) + + // Send a batch which is all expired + id2, err := txm.orm.InsertMsg("blah", "", []byte{0x03}) + id3, err := txm.orm.InsertMsg("blah", "", []byte{0x03}) + require.NoError(t, err) + time.Sleep(1 * time.Millisecond) + txm.sendMsgBatch(context.Background()) + require.NoError(t, err) + ms, err := txm.orm.GetMsgs(id2, id3) + assert.Equal(t, terradb.Errored, ms[0].State) + assert.Equal(t, terradb.Errored, ms[1].State) + }) } diff --git a/core/chains/terra/terratxm/txm_test.go b/core/chains/terra/terratxm/txm_test.go index b9eb5d829c4..0fcb29631a7 100644 --- a/core/chains/terra/terratxm/txm_test.go +++ b/core/chains/terra/terratxm/txm_test.go @@ -23,6 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/core/chains/terra" "github.com/smartcontractkit/chainlink/core/chains/terra/terratxm" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore" @@ -51,11 +52,11 @@ func TestTxm_Integration(t *testing.T) { chainCfg := pkgterra.NewConfig(dbChain.Cfg, lggr) orm := terratxm.NewORM(chainID, db, lggr, logCfg) eb := pg.NewEventBroadcaster(cfg.DatabaseURL(), 0, 0, lggr, uuid.NewV4()) - require.NoError(t, eb.Start()) + require.NoError(t, eb.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, eb.Close()) }) ks := keystore.New(db, utils.FastScryptParams, lggr, pgtest.NewPGCfg(true)) - accounts, testdir := terraclient.SetupLocalTerraNode(t, "42") - tc, err := terraclient.NewClient("42", "http://127.0.0.1:26657", terra.DefaultRequestTimeout, lggr) + accounts, testdir, tendermintURL := terraclient.SetupLocalTerraNode(t, "42") + tc, err := terraclient.NewClient("42", tendermintURL, terra.DefaultRequestTimeout, lggr) require.NoError(t, err) // First create a transmitter key and fund it with 1k uluna @@ -72,7 +73,7 @@ func TestTxm_Integration(t *testing.T) { // TODO: find a way to pull this test artifact from // the chainlink-terra repo instead of copying it to cores testdata - contractID := terraclient.DeployTestContract(t, accounts[0], terraclient.Account{ + contractID := terraclient.DeployTestContract(t, tendermintURL, accounts[0], terraclient.Account{ Name: "transmitter", PrivateKey: terratxm.NewKeyWrapper(transmitterKey), Address: transmitterID, @@ -81,13 +82,11 @@ func TestTxm_Integration(t *testing.T) { tcFn := func() (terraclient.ReaderWriter, error) { return tc, nil } // Start txm txm := terratxm.NewTxm(db, tcFn, *gpe, chainID, chainCfg, ks.Terra(), lggr, pgtest.NewPGCfg(true), eb) - require.NoError(t, txm.Start()) + require.NoError(t, txm.Start(testutils.Context(t))) // Change the contract state setMsg := wasmtypes.NewMsgExecuteContract(transmitterID, contractID, []byte(`{"reset":{"count":5}}`), sdk.Coins{}) - validBytes, err := setMsg.Marshal() - require.NoError(t, err) - _, err = txm.Enqueue(contractID.String(), validBytes) + _, err = txm.Enqueue(contractID.String(), setMsg) require.NoError(t, err) // Observe the counter gets set eventually @@ -99,25 +98,25 @@ func TestTxm_Integration(t *testing.T) { }, 10*time.Second, time.Second).Should(gomega.BeTrue()) // Ensure messages are completed gomega.NewWithT(t).Eventually(func() bool { - msgs, err := orm.SelectMsgsWithState(Confirmed, 5) + msgs, err := orm.GetMsgsState(Confirmed, 5) require.NoError(t, err) return 1 == len(msgs) }, 5*time.Second, time.Second).Should(gomega.BeTrue()) // Ensure invalid msgs are marked as errored invalidMsg := wasmtypes.NewMsgExecuteContract(transmitterID, contractID, []byte(`{"blah":{"blah":5}}`), sdk.Coins{}) - invalidBytes, err := invalidMsg.Marshal() + _, err = txm.Enqueue(contractID.String(), invalidMsg) + require.NoError(t, err) + _, err = txm.Enqueue(contractID.String(), invalidMsg) require.NoError(t, err) - _, err = txm.Enqueue(contractID.String(), invalidBytes) - _, err = txm.Enqueue(contractID.String(), invalidBytes) - _, err = txm.Enqueue(contractID.String(), validBytes) + _, err = txm.Enqueue(contractID.String(), setMsg) require.NoError(t, err) // Ensure messages are completed gomega.NewWithT(t).Eventually(func() bool { - succeeded, err := orm.SelectMsgsWithState(Confirmed, 5) + succeeded, err := orm.GetMsgsState(Confirmed, 5) require.NoError(t, err) - errored, err := orm.SelectMsgsWithState(Errored, 5) + errored, err := orm.GetMsgsState(Errored, 5) require.NoError(t, err) t.Log("errored", len(errored), "succeeded", len(succeeded)) return 2 == len(succeeded) && 2 == len(errored) diff --git a/core/cmd/app.go b/core/cmd/app.go index 33bec7c756e..1eec620dea8 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -57,6 +57,10 @@ func NewApp(client *Client) *cli.App { Name: "file, f", Usage: "text file holding the API email and password needed to create a session cookie", }, + cli.BoolFlag{ + Name: "bypass-version-check", + Usage: "Bypass versioning check for compatibility of remote node", + }, }, }, }, @@ -92,8 +96,13 @@ func NewApp(client *Client) *cli.App { Action: client.ReplayFromBlock, Flags: []cli.Flag{ cli.IntFlag{ - Name: "block-number", - Usage: "Block number to replay from", + Name: "block-number", + Usage: "Block number to replay from", + Required: true, + }, + cli.BoolFlag{ + Name: "force", + Usage: "Whether to force broadcasting logs which were already consumed and that would otherwise be skipped", }, }, }, @@ -888,7 +897,6 @@ func NewApp(client *Client) *cli.App { }, }, }, - { Name: "initiators", Usage: "Commands for managing External Initiators", @@ -914,43 +922,74 @@ func NewApp(client *Client) *cli.App { { Name: "txs", - Usage: "Commands for handling Ethereum transactions", + Usage: "Commands for handling transactions", Subcommands: []cli.Command{ { - Name: "create", - Usage: "Send ETH (or wei) from node ETH account to destination .", - Action: client.SendEther, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "force", - Usage: "allows to send a higher amount than the account's balance", + Name: "evm", + Usage: "Commands for handling EVM transactions", + Subcommands: []cli.Command{ + { + Name: "create", + Usage: "Send ETH (or wei) from node ETH account to destination .", + Action: client.SendEther, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "force", + Usage: "allows to send a higher amount than the account's balance", + }, + cli.BoolFlag{ + Name: "eth", + Usage: "allows to send ETH amounts (Default behavior)", + }, + cli.BoolFlag{ + Name: "wei", + Usage: "allows to send WEI amounts", + }, + cli.Int64Flag{ + Name: "id", + Usage: "chain ID", + }, + }, }, - cli.BoolFlag{ - Name: "eth", - Usage: "allows to send ETH amounts (Default behavior)", + { + Name: "list", + Usage: "List the Ethereum Transactions in descending order", + Action: client.IndexTransactions, + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "page", + Usage: "page of results to display", + }, + }, }, - cli.BoolFlag{ - Name: "wei", - Usage: "allows to send WEI amounts", + { + Name: "show", + Usage: "get information on a specific Ethereum Transaction", + Action: client.ShowTransaction, }, }, }, { - Name: "list", - Usage: "List the Ethereum Transactions in descending order", - Action: client.IndexTransactions, - Flags: []cli.Flag{ - cli.IntFlag{ - Name: "page", - Usage: "page of results to display", + Name: "terra", + Usage: "Commands for handling Terra transactions", + Subcommands: []cli.Command{ + { + Name: "create", + Usage: "Send Luna from node Terra account to destination .", + Action: client.TerraSendLuna, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "force", + Usage: "allows to send a higher amount than the account's balance", + }, + cli.StringFlag{ + Name: "id", + Usage: "chain ID", + }, + }, }, }, }, - { - Name: "show", - Usage: "get information on a specific Ethereum Transaction", - Action: client.ShowTransaction, - }, }, }, { @@ -1119,6 +1158,37 @@ func NewApp(client *Client) *cli.App { }, }, }, + { + Name: "forwarders", + Usage: "Commands for managing forwarder addresses.", + Subcommands: []cli.Command{ + { + Name: "list", + Usage: "List all stored forwarders addresses", + Action: client.ListForwarders, + }, + { + Name: "create", + Usage: "Create a new forwarder", + Action: client.CreateForwarder, + Flags: []cli.Flag{ + cli.Int64Flag{ + Name: "evmChainID, c", + Usage: "chain ID, if left empty, ETH_CHAIN_ID will be used", + }, + cli.StringFlag{ + Name: "address, a", + Usage: "The forwarding address (in hex format)", + }, + }, + }, + { + Name: "delete", + Usage: "Delete a forwarder address", + Action: client.DeleteForwarder, + }, + }, + }, }...) return app } diff --git a/core/cmd/bridge_commands_test.go b/core/cmd/bridge_commands_test.go index eb3263841ff..b67590ed44d 100644 --- a/core/cmd/bridge_commands_test.go +++ b/core/cmd/bridge_commands_test.go @@ -66,7 +66,7 @@ func TestClient_IndexBridges(t *testing.T) { client, r := app.NewClientAndRenderer() bt1 := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges1"), + Name: bridges.MustParseBridgeName("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } @@ -74,7 +74,7 @@ func TestClient_IndexBridges(t *testing.T) { require.NoError(t, err) bt2 := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges2"), + Name: bridges.MustParseBridgeName("testingbridges2"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } @@ -102,7 +102,7 @@ func TestClient_ShowBridge(t *testing.T) { client, r := app.NewClientAndRenderer() bt := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges1"), + Name: bridges.MustParseBridgeName("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } @@ -161,7 +161,7 @@ func TestClient_RemoveBridge(t *testing.T) { client, r := app.NewClientAndRenderer() bt := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges1"), + Name: bridges.MustParseBridgeName("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } diff --git a/core/cmd/client.go b/core/cmd/client.go index 6837fcb0c89..f28a2e10ac2 100644 --- a/core/cmd/client.go +++ b/core/cmd/client.go @@ -63,6 +63,7 @@ type Client struct { Renderer Config config.GeneralConfig Logger logger.Logger + CloseLogger func() error AppFactory AppFactory KeyStoreAuthenticator TerminalKeyStoreAuthenticator FallbackAPIInitializer APIInitializer @@ -92,7 +93,7 @@ type ChainlinkAppFactory struct{} // NewApplication returns a new instance of the node with the given config. func (n ChainlinkAppFactory) NewApplication(cfg config.GeneralConfig, db *sqlx.DB) (app chainlink.Application, err error) { - appLggr := logger.NewLogger() + appLggr, closeLggr := logger.NewLogger() keyStore := keystore.New(db, utils.GetScryptParams(cfg), appLggr, cfg) @@ -149,7 +150,7 @@ func (n ChainlinkAppFactory) NewApplication(cfg config.GeneralConfig, db *sqlx.D Config: cfg, Logger: appLggr, DB: db, - ORM: evm.NewORM(db), + ORM: evm.NewORM(db, appLggr, cfg), KeyStore: keyStore.Eth(), EventBroadcaster: eventBroadcaster, } @@ -182,6 +183,7 @@ func (n ChainlinkAppFactory) NewApplication(cfg config.GeneralConfig, db *sqlx.D Chains: chains, EventBroadcaster: eventBroadcaster, Logger: appLggr, + CloseLogger: closeLggr, ExternalInitiatorManager: externalInitiatorManager, Version: static.Version, }) @@ -200,7 +202,7 @@ func takeBackupIfVersionUpgrade(cfg config.GeneralConfig, lggr logger.Logger, ap lggr.Debugf("Application version %s is older or equal to database version %s, skipping automatic DB backup.", appv.String(), dbv.String()) return nil } - lggr.Infof("Upgrade detected: application version %s is newer than database version %s, taking automatic DB backup. To skip automatic databsae backup before version upgrades, set DATABASE_BACKUP_ON_VERSION_UPGRADE=false. To disable backups entirely set DATABASE_BACKUP_MODE=none.", appv.String(), dbv.String()) + lggr.Infof("Upgrade detected: application version %s is newer than database version %s, taking automatic DB backup. To skip automatic database backup before version upgrades, set DATABASE_BACKUP_ON_VERSION_UPGRADE=false. To disable backups entirely set DATABASE_BACKUP_MODE=none.", appv.String(), dbv.String()) databaseBackup, err := periodicbackup.NewDatabaseBackup(cfg, lggr) if err != nil { @@ -274,7 +276,7 @@ func tryRunServerUntilCancelled(ctx context.Context, lggr logger.Logger, cfg con for { // try calling runServer() and log error if any if err := runServer(); err != nil { - if err != http.ErrServerClosed { + if !errors.Is(err, http.ErrServerClosed) { lggr.Criticalf("Error starting server: %v", err) } } @@ -439,6 +441,7 @@ func (h *authenticatedHTTPClient) doRequest(verb, path string, body io.Reader, h type CookieAuthenticator interface { Cookie() (*http.Cookie, error) Authenticate(sessions.SessionRequest) (*http.Cookie, error) + Logout() error } type SessionCookieAuthenticatorConfig interface { @@ -499,10 +502,16 @@ func (t *SessionCookieAuthenticator) Authenticate(sessionRequest sessions.Sessio return sc, t.store.Save(sc) } +// Deletes any stored session +func (t *SessionCookieAuthenticator) Logout() error { + return t.store.Reset() +} + // CookieStore is a place to store and retrieve cookies. type CookieStore interface { Save(cookie *http.Cookie) error Retrieve() (*http.Cookie, error) + Reset() error } // MemoryCookieStore keeps a single cookie in memory @@ -516,6 +525,12 @@ func (m *MemoryCookieStore) Save(cookie *http.Cookie) error { return nil } +// Removes any stored cookie. +func (m *MemoryCookieStore) Reset() error { + m.Cookie = nil + return nil +} + // Retrieve returns any Saved cookies. func (m *MemoryCookieStore) Retrieve() (*http.Cookie, error) { return m.Cookie, nil @@ -535,6 +550,12 @@ func (d DiskCookieStore) Save(cookie *http.Cookie) error { return ioutil.WriteFile(d.cookiePath(), []byte(cookie.String()), 0600) } +// Removes any stored cookie. +func (d DiskCookieStore) Reset() error { + // Write empty bytes + return ioutil.WriteFile(d.cookiePath(), []byte(""), 0600) +} + // Retrieve returns any Saved cookies. func (d DiskCookieStore) Retrieve() (*http.Cookie, error) { b, err := ioutil.ReadFile(d.cookiePath()) diff --git a/core/cmd/client_test.go b/core/cmd/client_test.go index 70f2a1365f6..9707a3530d7 100644 --- a/core/cmd/client_test.go +++ b/core/cmd/client_test.go @@ -6,6 +6,7 @@ import ( "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/sessions" @@ -50,7 +51,7 @@ func TestTerminalCookieAuthenticator_AuthenticateWithSession(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) tests := []struct { name, email, pwd string @@ -141,7 +142,7 @@ func TestTerminalAPIInitializer_InitializeWithoutAPIUser(t *testing.T) { db := pgtest.NewSqlxDB(t) orm := sessions.NewORM(db, time.Minute, logger.TestLogger(t)) - mock := &cltest.MockCountingPrompter{EnteredStrings: test.enteredStrings, NotTerminal: !test.isTerminal} + mock := &cltest.MockCountingPrompter{T: t, EnteredStrings: test.enteredStrings, NotTerminal: !test.isTerminal} tai := cmd.NewPromptingAPIInitializer(mock) // Remove fixture user @@ -174,7 +175,7 @@ func TestTerminalAPIInitializer_InitializeWithExistingAPIUser(t *testing.T) { initialUser := cltest.MustRandomUser(t) require.NoError(t, orm.CreateUser(&initialUser)) - mock := &cltest.MockCountingPrompter{} + mock := &cltest.MockCountingPrompter{T: t} tai := cmd.NewPromptingAPIInitializer(mock) user, err := tai.Initialize(orm) @@ -254,7 +255,7 @@ func TestPromptingSessionRequestBuilder(t *testing.T) { for _, test := range tests { t.Run(test.email, func(t *testing.T) { enteredStrings := []string{test.email, test.pwd} - prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} builder := cmd.NewPromptingSessionRequestBuilder(prompter) sr, err := builder.Build("") diff --git a/core/cmd/eth_keys_commands_test.go b/core/cmd/eth_keys_commands_test.go index b455e8f3f78..033075475be 100644 --- a/core/cmd/eth_keys_commands_test.go +++ b/core/cmd/eth_keys_commands_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/urfave/cli" - null "gopkg.in/guregu/null.v4" + "gopkg.in/guregu/null.v4" ) func TestEthKeysPresenter_RenderTable(t *testing.T) { @@ -83,8 +83,7 @@ func TestEthKeysPresenter_RenderTable(t *testing.T) { func TestClient_ListETHKeys(t *testing.T) { t.Parallel() - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) app := startNewApplication(t, @@ -109,8 +108,7 @@ func TestClient_ListETHKeys(t *testing.T) { func TestClient_CreateETHKey(t *testing.T) { t.Parallel() - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) app := startNewApplication(t, @@ -164,8 +162,7 @@ func TestClient_CreateETHKey(t *testing.T) { func TestClient_UpdateETHKey(t *testing.T) { t.Parallel() - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) app := startNewApplication(t, @@ -202,8 +199,7 @@ func TestClient_UpdateETHKey(t *testing.T) { func TestClient_DeleteETHKey(t *testing.T) { t.Parallel() - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) app := startNewApplication(t, @@ -240,8 +236,7 @@ func TestClient_ImportExportETHKey_NoChains(t *testing.T) { t.Cleanup(func() { deleteKeyExportFile(t) }) - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) app := startNewApplication(t, @@ -257,6 +252,7 @@ func TestClient_ImportExportETHKey_NoChains(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("file", "internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) require.NoError(t, err) @@ -320,6 +316,7 @@ func TestClient_ImportExportETHKey_NoChains(t *testing.T) { set = flag.NewFlagSet("test Eth export invalid id", 0) set.Parse([]string{"999"}) set.String("newpassword", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") set.String("output", keyName, "") c = cli.NewContext(nil, set, nil) err = client.ExportETHKey(c) @@ -331,8 +328,7 @@ func TestClient_ImportExportETHKey_WithChains(t *testing.T) { t.Cleanup(func() { deleteKeyExportFile(t) }) - ethClient, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethClient := newEthMock(t) app := startNewApplication(t, withMocks(ethClient), withConfigSet(func(c *configtest.TestGeneralConfig) { @@ -350,6 +346,7 @@ func TestClient_ImportExportETHKey_WithChains(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("file", "internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) require.NoError(t, err) @@ -411,6 +408,7 @@ func TestClient_ImportExportETHKey_WithChains(t *testing.T) { set = flag.NewFlagSet("test Eth export invalid id", 0) set.Parse([]string{"999"}) set.String("newpassword", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") set.String("output", keyName, "") c = cli.NewContext(nil, set, nil) err = client.ExportETHKey(c) diff --git a/core/cmd/evm_chains_commands_test.go b/core/cmd/evm_chains_commands_test.go index 09d47bdb970..9dfc91113c8 100644 --- a/core/cmd/evm_chains_commands_test.go +++ b/core/cmd/evm_chains_commands_test.go @@ -12,10 +12,15 @@ import ( "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/utils" ) +func newRandChainID() *utils.Big { + return utils.NewBig(testutils.NewRandomEVMChainID()) +} + func TestClient_IndexEVMChains(t *testing.T) { t.Parallel() @@ -32,7 +37,7 @@ func TestClient_IndexEVMChains(t *testing.T) { _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - id := utils.NewBigI(99) + id := newRandChainID() chain, err := orm.CreateChain(*id, types.ChainCfg{}) require.NoError(t, err) @@ -61,7 +66,8 @@ func TestClient_CreateEVMChain(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("cli", 0) - set.Int64("id", 99, "") + id := newRandChainID() + set.Int64("id", id.ToInt().Int64(), "") set.Parse([]string{`{}`}) c := cli.NewContext(nil, set, nil) @@ -72,7 +78,7 @@ func TestClient_CreateEVMChain(t *testing.T) { require.NoError(t, err) require.Len(t, chains, initialCount+1) ch := chains[initialCount] - assert.Equal(t, int64(99), ch.ID.ToInt().Int64()) + assert.Equal(t, id.ToInt().Int64(), ch.ID.ToInt().Int64()) assertTableRenders(t, r) } @@ -92,7 +98,7 @@ func TestClient_RemoveEVMChain(t *testing.T) { _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - id := utils.NewBigI(99) + id := newRandChainID() _, err = orm.CreateChain(*id, types.ChainCfg{}) require.NoError(t, err) chains, _, err := orm.Chains(0, 25) @@ -100,7 +106,7 @@ func TestClient_RemoveEVMChain(t *testing.T) { require.Len(t, chains, initialCount+1) set := flag.NewFlagSet("cli", 0) - set.Parse([]string{"99"}) + set.Parse([]string{id.String()}) c := cli.NewContext(nil, set, nil) err = client.RemoveEVMChain(c) @@ -129,7 +135,7 @@ func TestClient_ConfigureEVMChain(t *testing.T) { _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - id := utils.NewBigI(99) + id := newRandChainID() _, err = orm.CreateChain(*id, types.ChainCfg{ BlockHistoryEstimatorBlockDelay: null.IntFrom(5), EvmFinalityDepth: null.IntFrom(5), @@ -141,7 +147,7 @@ func TestClient_ConfigureEVMChain(t *testing.T) { require.Len(t, chains, initialCount+1) set := flag.NewFlagSet("cli", 0) - set.Int64("id", 99, "param") + set.Int64("id", id.ToInt().Int64(), "param") set.Parse([]string{"BlockHistoryEstimatorBlockDelay=9", "EvmGasBumpPercent=null"}) c := cli.NewContext(nil, set, nil) diff --git a/core/cmd/evm_node_commands.go b/core/cmd/evm_node_commands.go index b3977802768..09a9a6c4040 100644 --- a/core/cmd/evm_node_commands.go +++ b/core/cmd/evm_node_commands.go @@ -29,11 +29,12 @@ func (p *EVMNodePresenter) ToRow() []string { p.HTTPURL.ValueOrZero(), p.CreatedAt.String(), p.UpdatedAt.String(), + p.State, } return row } -var evmNodeHeaders = []string{"ID", "Name", "Chain ID", "Websocket URL", "HTTP URL", "Created", "Updated"} +var evmNodeHeaders = []string{"ID", "Name", "Chain ID", "Websocket URL", "HTTP URL", "Created", "Updated", "State"} // RenderTable implements TableRenderer func (p EVMNodePresenter) RenderTable(rt RendererTable) error { diff --git a/core/cmd/evm_node_commands_test.go b/core/cmd/evm_node_commands_test.go index fbd46598b5f..6ebbb2da66e 100644 --- a/core/cmd/evm_node_commands_test.go +++ b/core/cmd/evm_node_commands_test.go @@ -83,8 +83,8 @@ func TestClient_CreateEVMNode(t *testing.T) { set := flag.NewFlagSet("cli", 0) set.String("name", "Example", "") set.String("type", "primary", "") - set.String("ws-url", "ws://", "") - set.String("http-url", "http://", "") + set.String("ws-url", "ws://TestClient_CreateEVMNode1.invalid", "") + set.String("http-url", "http://TestClient_CreateEVMNode2.invalid", "") set.Int64("chain-id", chain.ID.ToInt().Int64(), "") c := cli.NewContext(nil, set, nil) err = client.CreateEVMNode(c) @@ -94,7 +94,7 @@ func TestClient_CreateEVMNode(t *testing.T) { set = flag.NewFlagSet("cli", 0) set.String("name", "Send only", "") set.String("type", "sendonly", "") - set.String("http-url", "http://", "") + set.String("http-url", "http://TestClient_CreateEVMNode3.invalid", "") set.Int64("chain-id", chain.ID.ToInt().Int64(), "") c = cli.NewContext(nil, set, nil) err = client.CreateEVMNode(c) @@ -106,14 +106,14 @@ func TestClient_CreateEVMNode(t *testing.T) { n := nodes[initialNodesCount] assert.Equal(t, "Example", n.Name) assert.Equal(t, false, n.SendOnly) - assert.Equal(t, null.StringFrom("ws://"), n.WSURL) - assert.Equal(t, null.StringFrom("http://"), n.HTTPURL) + assert.Equal(t, null.StringFrom("ws://TestClient_CreateEVMNode1.invalid"), n.WSURL) + assert.Equal(t, null.StringFrom("http://TestClient_CreateEVMNode2.invalid"), n.HTTPURL) assert.Equal(t, chain.ID, n.EVMChainID) n = nodes[initialNodesCount+1] assert.Equal(t, "Send only", n.Name) assert.Equal(t, true, n.SendOnly) assert.Equal(t, null.String{}, n.WSURL) - assert.Equal(t, null.StringFrom("http://"), n.HTTPURL) + assert.Equal(t, null.StringFrom("http://TestClient_CreateEVMNode3.invalid"), n.HTTPURL) assert.Equal(t, chain.ID, n.EVMChainID) assertTableRenders(t, r) diff --git a/core/cmd/transaction_commands.go b/core/cmd/evm_transaction_commands.go similarity index 86% rename from core/cmd/transaction_commands.go rename to core/cmd/evm_transaction_commands.go index 36ccbbcc745..10e04f25139 100644 --- a/core/cmd/transaction_commands.go +++ b/core/cmd/evm_transaction_commands.go @@ -5,14 +5,16 @@ import ( "encoding/json" "errors" "fmt" + "math/big" + + "github.com/urfave/cli" + "go.uber.org/multierr" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/utils/stringutils" "github.com/smartcontractkit/chainlink/core/web/presenters" - "github.com/urfave/cli" - "go.uber.org/multierr" ) type EthTxPresenter struct { @@ -57,7 +59,7 @@ func (ps EthTxPresenters) RenderTable(rt RendererTable) error { // IndexTransactions returns the list of transactions in descending order, // taking an optional page parameter func (cli *Client) IndexTransactions(c *cli.Context) error { - return cli.getPage("/v2/transactions", c.Int("page"), &EthTxPresenters{}) + return cli.getPage("/v2/transactions/evm", c.Int("page"), &EthTxPresenters{}) } // ShowTransaction returns the info for the given transaction hash @@ -66,7 +68,7 @@ func (cli *Client) ShowTransaction(c *cli.Context) (err error) { return cli.errorOut(errors.New("must pass the hash of the transaction")) } hash := c.Args().First() - resp, err := cli.HTTP.Get("/v2/transactions/" + hash) + resp, err := cli.HTTP.Get("/v2/transactions/evm/" + hash) if err != nil { return cli.errorOut(err) } @@ -83,13 +85,13 @@ func (cli *Client) ShowTransaction(c *cli.Context) (err error) { // IndexTxAttempts returns the list of transactions in descending order, // taking an optional page parameter func (cli *Client) IndexTxAttempts(c *cli.Context) error { - return cli.getPage("/v2/tx_attempts", c.Int("page"), &EthTxPresenters{}) + return cli.getPage("/v2/tx_attempts/evm", c.Int("page"), &EthTxPresenters{}) } // SendEther transfers ETH from the node's account to a specified address. func (cli *Client) SendEther(c *cli.Context) (err error) { if c.NArg() < 3 { - return cli.errorOut(errors.New("sendether expects three arguments: amount, fromAddress and toAddress")) + return cli.errorOut(errors.New("three arguments expected: amount, fromAddress and toAddress")) } var amount assets.Eth @@ -128,10 +130,21 @@ func (cli *Client) SendEther(c *cli.Context) (err error) { unparsedDestinationAddress), err)) } + var evmChainID *big.Int + if c.IsSet("id") { + s := c.String("id") + var ok bool + evmChainID, ok = new(big.Int).SetString(s, 10) + if !ok { + return cli.errorOut(errors.New("")) + } + } + request := models.SendEtherRequest{ DestinationAddress: destinationAddress, FromAddress: fromAddress, Amount: amount, + EVMChainID: (*utils.Big)(evmChainID), AllowHigherAmounts: c.IsSet("force"), } @@ -142,7 +155,7 @@ func (cli *Client) SendEther(c *cli.Context) (err error) { buf := bytes.NewBuffer(requestData) - resp, err := cli.HTTP.Post("/v2/transfers", buf) + resp, err := cli.HTTP.Post("/v2/transfers/evm", buf) if err != nil { return cli.errorOut(err) } diff --git a/core/cmd/transaction_commands_test.go b/core/cmd/evm_transaction_commands_test.go similarity index 88% rename from core/cmd/transaction_commands_test.go rename to core/cmd/evm_transaction_commands_test.go index 3cf12d0067b..4681dcba265 100644 --- a/core/cmd/transaction_commands_test.go +++ b/core/cmd/evm_transaction_commands_test.go @@ -5,16 +5,17 @@ import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/urfave/cli" - null "gopkg.in/guregu/null.v4" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink/core/assets" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/core/cmd" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" ) func TestClient_IndexTransactions(t *testing.T) { @@ -25,7 +26,7 @@ func TestClient_IndexTransactions(t *testing.T) { _, from := cltest.MustAddRandomKeyToKeystore(t, app.KeyStore.Eth()) - tx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, app.BPTXMORM(), 0, 1, from) + tx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, app.TxmORM(), 0, 1, from) attempt := tx.EthTxAttempts[0] // page 1 @@ -59,7 +60,7 @@ func TestClient_ShowTransaction(t *testing.T) { db := app.GetSqlxDB() _, from := cltest.MustAddRandomKeyToKeystore(t, app.KeyStore.Eth()) - borm := cltest.NewBulletproofTxManagerORM(t, db, app.GetConfig()) + borm := cltest.NewTxmORM(t, db, app.GetConfig()) tx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, borm, 0, 1, from) attempt := tx.EthTxAttempts[0] @@ -80,7 +81,7 @@ func TestClient_IndexTxAttempts(t *testing.T) { _, from := cltest.MustAddRandomKeyToKeystore(t, app.KeyStore.Eth()) - tx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, app.BPTXMORM(), 0, 1, from) + tx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, app.TxmORM(), 0, 1, from) // page 1 set := flag.NewFlagSet("test txattempts", 0) @@ -104,7 +105,7 @@ func TestClient_IndexTxAttempts(t *testing.T) { assert.Equal(t, 0, len(renderedAttempts)) } -func TestClient_SendEther_From_BPTXM(t *testing.T) { +func TestClient_SendEther_From_Txm(t *testing.T) { t.Parallel() key := cltest.MustGenerateRandomKey(t) @@ -113,8 +114,7 @@ func TestClient_SendEther_From_BPTXM(t *testing.T) { balance, err := assets.NewEthValueS("200") require.NoError(t, err) - ethMock, assertMocksCalled := newEthMockWithTransactionsOnBlocksAssertions(t) - defer assertMocksCalled() + ethMock := newEthMockWithTransactionsOnBlocksAssertions(t) ethMock.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(balance.ToInt(), nil) @@ -140,7 +140,7 @@ func TestClient_SendEther_From_BPTXM(t *testing.T) { assert.NoError(t, client.SendEther(c)) - etx := bulletprooftxmanager.EthTx{} + etx := txmgr.EthTx{} require.NoError(t, db.Get(&etx, `SELECT * FROM eth_txes`)) require.Equal(t, "100.500000000000000000", etx.Value.String()) require.Equal(t, fromAddress, etx.FromAddress) @@ -152,7 +152,7 @@ func TestClient_SendEther_From_BPTXM(t *testing.T) { assert.Equal(t, etx.Value.String(), output.Value) } -func TestClient_SendEther_From_BPTXM_WEI(t *testing.T) { +func TestClient_SendEther_From_Txm_WEI(t *testing.T) { t.Parallel() key := cltest.MustGenerateRandomKey(t) @@ -161,8 +161,7 @@ func TestClient_SendEther_From_BPTXM_WEI(t *testing.T) { balance, err := assets.NewEthValueS("200") require.NoError(t, err) - ethMock, assertMocksCalled := newEthMockWithTransactionsOnBlocksAssertions(t) - defer assertMocksCalled() + ethMock := newEthMockWithTransactionsOnBlocksAssertions(t) ethMock.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(balance.ToInt(), nil) @@ -193,7 +192,7 @@ func TestClient_SendEther_From_BPTXM_WEI(t *testing.T) { assert.NoError(t, client.SendEther(c)) - etx := bulletprooftxmanager.EthTx{} + etx := txmgr.EthTx{} require.NoError(t, db.Get(&etx, `SELECT * FROM eth_txes`)) require.Equal(t, "1.000000000000000000", etx.Value.String()) require.Equal(t, fromAddress, etx.FromAddress) diff --git a/core/cmd/forwarders_commands.go b/core/cmd/forwarders_commands.go new file mode 100644 index 00000000000..2776db81309 --- /dev/null +++ b/core/cmd/forwarders_commands.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "time" + + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/utils" + "github.com/smartcontractkit/chainlink/core/web" + "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/urfave/cli" + "go.uber.org/multierr" +) + +type EVMForwarderPresenter struct { + JAID // This is needed to render the id for a JSONAPI Resource as normal JSON + presenters.EVMForwarderResource +} + +var evmFwdsHeaders = []string{"ID", "Address", "Chain ID", "Created At"} + +// ToRow presents the EVMForwarderResource as a slice of strings. +func (p *EVMForwarderPresenter) ToRow() []string { + row := []string{ + p.GetID(), + p.Address.String(), + p.EVMChainID.ToInt().String(), + p.CreatedAt.Format(time.RFC3339), + } + return row +} + +// RenderTable implements TableRenderer +func (p *EVMForwarderPresenter) RenderTable(rt RendererTable) error { + var rows [][]string + rows = append(rows, p.ToRow()) + renderList(evmFwdsHeaders, rows, rt.Writer) + + return nil +} + +// EVMForwarderPresenters implements TableRenderer for a slice of EVMForwarderPresenter. +type EVMForwarderPresenters []EVMForwarderPresenter + +// RenderTable implements TableRenderer +func (ps EVMForwarderPresenters) RenderTable(rt RendererTable) error { + var rows [][]string + + for _, p := range ps { + rows = append(rows, p.ToRow()) + } + + renderList(evmFwdsHeaders, rows, rt.Writer) + + return nil +} + +// ListForwarders list all forwarder addresses tracked by node +func (cli *Client) ListForwarders(c *cli.Context) (err error) { + return cli.getPage("/v2/nodes/evm/forwarders", c.Int("page"), &EVMForwarderPresenters{}) +} + +// DeleteForwarder deletes forwarder address from node db by id. +func (cli *Client) DeleteForwarder(c *cli.Context) (err error) { + if !c.Args().Present() { + return cli.errorOut(errors.New("must pass the forwarder id to be archived")) + } + resp, err := cli.HTTP.Delete("/v2/nodes/evm/forwarders/" + c.Args().First()) + if err != nil { + return cli.errorOut(err) + } + _, err = cli.parseResponse(resp) + if err != nil { + return cli.errorOut(err) + } + + fmt.Printf("Forwarder %v Deleted\n", c.Args().First()) + return nil +} + +// AddForwarder adds forwarder address to node db. +func (cli *Client) CreateForwarder(c *cli.Context) (err error) { + addressHex := c.String("address") + chainIDStr := c.String("evmChainID") + + addressBytes, err := hexutil.Decode(addressHex) + if err != nil { + return cli.errorOut(errors.Wrap(err, "could not decode address")) + } + address := gethCommon.BytesToAddress(addressBytes) + + var chainID *big.Int + if chainIDStr != "" { + var ok bool + chainID, ok = big.NewInt(0).SetString(chainIDStr, 10) + if !ok { + return cli.errorOut(errors.Wrap(err, "invalid evmChainID")) + } + } + + request, err := json.Marshal(web.CreateEVMForwarderRequest{ + EVMChainID: (*utils.Big)(chainID), + Address: address, + }) + if err != nil { + return cli.errorOut(err) + } + + resp, err := cli.HTTP.Post("/v2/nodes/evm/forwarders", bytes.NewReader(request)) + if err != nil { + return cli.errorOut(err) + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + err = multierr.Append(err, cerr) + } + }() + + if resp.StatusCode >= 400 { + body, rerr := ioutil.ReadAll(resp.Body) + if err != nil { + err = multierr.Append(err, rerr) + return cli.errorOut(err) + } + fmt.Printf("Response: '%v', Status: %d\n", string(body), resp.StatusCode) + return cli.errorOut(err) + } + + err = cli.renderAPIResponse(resp, &EVMForwarderPresenter{}, "Forwarder created") + return err +} diff --git a/core/cmd/forwarders_commands_test.go b/core/cmd/forwarders_commands_test.go new file mode 100644 index 00000000000..bac37a0a434 --- /dev/null +++ b/core/cmd/forwarders_commands_test.go @@ -0,0 +1,151 @@ +package cmd_test + +import ( + "bytes" + "flag" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/cmd" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/core/utils" + "github.com/smartcontractkit/chainlink/core/web/presenters" +) + +func TestEVMForwarderPresenter_RenderTable(t *testing.T) { + t.Parallel() + + var ( + id = "1" + address = common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81") + evmChainID = utils.NewBigI(4) + createdAt = time.Now() + updatedAt = time.Now().Add(time.Second) + buffer = bytes.NewBufferString("") + r = cmd.RendererTable{Writer: buffer} + ) + + p := cmd.EVMForwarderPresenter{ + EVMForwarderResource: presenters.EVMForwarderResource{ + JAID: presenters.NewJAID(id), + Address: address, + EVMChainID: *evmChainID, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, + } + + // Render a single resource + require.NoError(t, p.RenderTable(r)) + + output := buffer.String() + assert.Contains(t, output, id) + assert.Contains(t, output, address.String()) + assert.Contains(t, output, evmChainID.ToInt().String()) + assert.Contains(t, output, createdAt.Format(time.RFC3339)) + + // Render many resources + buffer.Reset() + ps := cmd.EVMForwarderPresenters{p} + require.NoError(t, ps.RenderTable(r)) + + output = buffer.String() + assert.Contains(t, output, id) + assert.Contains(t, output, address.String()) + assert.Contains(t, output, evmChainID.ToInt().String()) + assert.Contains(t, output, createdAt.Format(time.RFC3339)) +} + +func TestClient_CreateEVMForwarder(t *testing.T) { + t.Parallel() + + app := startNewApplication(t, withConfigSet(func(c *configtest.TestGeneralConfig) { + c.Overrides.EVMEnabled = null.BoolFrom(true) + })) + client, r := app.NewClientAndRenderer() + + // Create chain + orm := app.EVMORM() + id := newRandChainID() + chain, err := orm.CreateChain(*id, types.ChainCfg{}) + require.NoError(t, err) + + // Create the fwdr + set := flag.NewFlagSet("test", 0) + set.String("file", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") + set.String("address", "0x5431F5F973781809D18643b87B44921b11355d81", "") + set.Int("evmChainID", int(chain.ID.ToInt().Int64()), "") + err = client.CreateForwarder(cli.NewContext(nil, set, nil)) + require.NoError(t, err) + require.Len(t, r.Renders, 1) + createOutput, ok := r.Renders[0].(*cmd.EVMForwarderPresenter) + require.True(t, ok, "Expected Renders[0] to be *cmd.EVMForwarderPresenter, got %T", r.Renders[0]) + + // Assert fwdr is listed + require.Nil(t, client.ListForwarders(cltest.EmptyCLIContext())) + fwds := *r.Renders[1].(*cmd.EVMForwarderPresenters) + require.Equal(t, 1, len(fwds)) + assert.Equal(t, createOutput.ID, fwds[0].ID) + + // Delete fwdr + set = flag.NewFlagSet("test", 0) + set.Parse([]string{createOutput.ID}) + c := cli.NewContext(nil, set, nil) + require.NoError(t, client.DeleteForwarder(c)) + + // Assert fwdr is not listed + require.Nil(t, client.ListForwarders(cltest.EmptyCLIContext())) + require.Len(t, r.Renders, 3) + fwds = *r.Renders[2].(*cmd.EVMForwarderPresenters) + require.Equal(t, 0, len(fwds)) +} + +func TestClient_CreateEVMForwarder_BadAddress(t *testing.T) { + t.Parallel() + + app := startNewApplication(t, withConfigSet(func(c *configtest.TestGeneralConfig) { + c.Overrides.EVMEnabled = null.BoolFrom(true) + })) + client, _ := app.NewClientAndRenderer() + + // Create chain + orm := app.EVMORM() + _, _, err := orm.Chains(0, 25) + require.NoError(t, err) + + id := newRandChainID() + chain, err := orm.CreateChain(*id, types.ChainCfg{}) + require.NoError(t, err) + + // Create the fwdr + set := flag.NewFlagSet("test", 0) + set.String("file", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") + set.String("address", "0xWrongFormatAddress", "") + set.Int("evmChainID", int(chain.ID.ToInt().Int64()), "") + err = client.CreateForwarder(cli.NewContext(nil, set, nil)) + require.Contains(t, err.Error(), "could not decode address: invalid hex string") +} + +func TestClient_DeleteEVMForwarders_MissingFwdId(t *testing.T) { + t.Parallel() + + app := startNewApplication(t, withConfigSet(func(c *configtest.TestGeneralConfig) { + c.Overrides.EVMEnabled = null.BoolFrom(true) + })) + client, _ := app.NewClientAndRenderer() + + // Delete fwdr without id + set := flag.NewFlagSet("test", 0) + c := cli.NewContext(nil, set, nil) + require.Equal(t, "must pass the forwarder id to be archived", client.DeleteForwarder(c).Error()) +} diff --git a/core/cmd/helpers_test.go b/core/cmd/helpers_test.go new file mode 100644 index 00000000000..df1eb61fd8d --- /dev/null +++ b/core/cmd/helpers_test.go @@ -0,0 +1,8 @@ +package cmd + +import "github.com/smartcontractkit/chainlink/core/logger" + +// CheckRemoteBuildCompatibility exposes checkRemoteBuildCompatibility for testing. +func (cli *Client) CheckRemoteBuildCompatibility(lggr logger.Logger, onlyWarn bool, cliVersion, cliSha string) error { + return cli.checkRemoteBuildCompatibility(lggr, onlyWarn, cliVersion, cliSha) +} diff --git a/core/cmd/local_client.go b/core/cmd/local_client.go index 328f5552b20..e38c9e7d68b 100644 --- a/core/cmd/local_client.go +++ b/core/cmd/local_client.go @@ -28,7 +28,9 @@ import ( "golang.org/x/sync/errgroup" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/sqlx" + + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -40,7 +42,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/migrate" "github.com/smartcontractkit/chainlink/core/utils" webPresenters "github.com/smartcontractkit/chainlink/core/web/presenters" - "github.com/smartcontractkit/sqlx" ) // ownerPermsMask are the file permission bits reserved for owner. @@ -51,7 +52,7 @@ func (cli *Client) RunNode(c *clipkg.Context) error { if err := cli.runNode(c); err != nil { err = errors.Wrap(err, "Cannot boot Chainlink") cli.Logger.Errorw(err.Error(), "err", err) - if serr := cli.Logger.Sync(); serr != nil { + if serr := cli.CloseLogger(); serr != nil { err = multierr.Combine(serr, err) } return cli.errorOut(err) @@ -83,10 +84,14 @@ func (cli *Client) runNode(c *clipkg.Context) error { var shutdownStartTime time.Time defer func() { close(cleanExit) - log.Printf("Graceful shutdown time: %s", time.Since(shutdownStartTime)) + if !shutdownStartTime.IsZero() { + log.Printf("Graceful shutdown time: %s", time.Since(shutdownStartTime)) + } }() - go shutdown.HandleShutdown(func() { + go shutdown.HandleShutdown(func(sig string) { + lggr.Infof("Shutting down due to %s signal received...", sig) + shutdownStartTime = time.Now() cancelRootCtx() @@ -102,8 +107,8 @@ func (cli *Client) runNode(c *clipkg.Context) error { if err = ldb.Close(); err != nil { lggr.Criticalf("Failed to close LockedDB: %v", err) } - if err = lggr.Sync(); err != nil { - log.Printf("Failed to sync Logger: %v", err) + if err = cli.CloseLogger(); err != nil { + log.Printf("Failed to close Logger: %v", err) } os.Exit(-1) @@ -209,11 +214,11 @@ func (cli *Client) runNode(c *clipkg.Context) error { } var user sessions.User - if _, err = NewFileAPIInitializer(c.String("api"), lggr).Initialize(sessionORM); err != nil && err != ErrNoCredentialFile { + if _, err = NewFileAPIInitializer(c.String("api"), lggr).Initialize(sessionORM); err != nil && !errors.Is(err, ErrNoCredentialFile) { return errors.Wrap(err, "error creating api initializer") } if user, err = cli.FallbackAPIInitializer.Initialize(sessionORM); err != nil { - if err == ErrorNoAPICredentialsAvailable { + if errors.Is(err, ErrorNoAPICredentialsAvailable) { return errors.WithStack(err) } return errors.Wrap(err, "error creating fallback initializer") @@ -221,12 +226,15 @@ func (cli *Client) runNode(c *clipkg.Context) error { lggr.Info("API exposed for user ", user.Email) - grp, grpCtx := errgroup.WithContext(rootCtx) - - if err = app.Start(); err != nil { + if err = app.Start(rootCtx); err != nil { + // We do not try stopping any sub-services that might be started, + // because the app will exit immediately upon return. + // But LockedDB will be released by defer in above. return errors.Wrap(err, "error starting app") } + grp, grpCtx := errgroup.WithContext(rootCtx) + grp.Go(func() error { <-grpCtx.Done() if errInternal := app.Stop(); errInternal != nil { @@ -241,7 +249,7 @@ func (cli *Client) runNode(c *clipkg.Context) error { grp.Go(func() error { errInternal := cli.Runner.Run(grpCtx, app) - if errInternal == http.ErrServerClosed { + if errors.Is(errInternal, http.ErrServerClosed) { errInternal = nil } // In tests we have custom runners that stop the app gracefully, @@ -389,7 +397,7 @@ func (cli *Client) RebroadcastTransactions(c *clipkg.Context) (err error) { if err != nil { return cli.errorOut(err) } - ec := bulletprooftxmanager.NewEthConfirmer(app.GetSqlxDB(), ethClient, chain.Config(), keyStore.Eth(), keyStates, nil, nil, chain.Logger()) + ec := txmgr.NewEthConfirmer(app.GetSqlxDB(), ethClient, chain.Config(), keyStore.Eth(), keyStates, nil, nil, chain.Logger()) err = ec.ForceRebroadcast(beginningNonce, endingNonce, gasPriceWei, address, overrideGasLimit) return cli.errorOut(err) } diff --git a/core/cmd/local_client_test.go b/core/cmd/local_client_test.go index d45599b5c14..d81c51d00d9 100644 --- a/core/cmd/local_client_test.go +++ b/core/cmd/local_client_test.go @@ -7,7 +7,6 @@ import ( "math/big" "os" "path/filepath" - "strings" "testing" "time" @@ -26,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/sessions" "github.com/smartcontractkit/chainlink/core/store/dialects" + "github.com/smartcontractkit/chainlink/core/utils" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/kylelemons/godebug/diff" @@ -46,11 +46,13 @@ func TestClient_RunNodeShowsEnv(t *testing.T) { require.Empty(t, invalid) require.Equal(t, zapcore.InfoLevel, ll) - cfg := config.NewGeneralConfig(logger.TestLogger(t)) + lggr := logger.TestLogger(t) + + cfg := config.NewGeneralConfig(lggr) require.NoError(t, cfg.SetLogLevel(zapcore.DebugLevel)) db := pgtest.NewSqlxDB(t) - sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t)) + sessionORM := sessions.NewORM(db, time.Minute, lggr) keyStore := cltest.NewKeyStore(t, db, cfg) _, err := keyStore.Eth().Create(&cltest.FixtureChainID) require.NoError(t, err) @@ -63,20 +65,31 @@ func TestClient_RunNodeShowsEnv(t *testing.T) { app.On("SessionORM").Return(sessionORM) app.On("GetKeyStore").Return(keyStore) app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, ethClient, evmtest.NewChainScopedConfig(t, cfg))}).Maybe() - app.On("Start").Return(nil) + app.On("Start", mock.Anything).Return(nil) app.On("Stop").Return(nil) app.On("ID").Return(uuid.NewV4()) + var logFileSize utils.FileSize + err = logFileSize.UnmarshalText([]byte("100mb")) + assert.NoError(t, err) + lcfg := logger.Config{ - LogLevel: zapcore.DebugLevel, - ToDisk: true, - Dir: t.TempDir(), + LogLevel: zapcore.DebugLevel, + FileMaxSize: int(logFileSize), + Dir: t.TempDir(), } + tmpFile, err := os.CreateTemp(lcfg.Dir, "*") + assert.NoError(t, err) + defer tmpFile.Close() + + newLogger, closeLogger := lcfg.New() + runner := cltest.BlockedRunner{Done: make(chan struct{})} client := cmd.Client{ Config: cfg, - Logger: lcfg.New(), + Logger: newLogger, + CloseLogger: closeLogger, AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), Runner: runner, @@ -98,14 +111,6 @@ func TestClient_RunNodeShowsEnv(t *testing.T) { } awaiter.AwaitOrFail(t) - require.NoError(t, client.Logger.Sync()) - - logs, err := cltest.ReadLogs(lcfg.Dir) - require.NoError(t, err) - msg := cltest.FindLogMessage(logs, func(msg string) bool { - return strings.HasPrefix(msg, "Environment variables") - }) - require.NotEmpty(t, msg, "No env var message found") expected := fmt.Sprintf(`Environment variables ADVISORY_LOCK_CHECK_INTERVAL: 1s @@ -121,7 +126,7 @@ CLIENT_NODE_URL: http://localhost:6688 DATABASE_BACKUP_FREQUENCY: 1h0m0s DATABASE_BACKUP_MODE: none DATABASE_BACKUP_ON_VERSION_UPGRADE: true -DATABASE_LOCKING_MODE: advisorylock +DATABASE_LOCKING_MODE: dual ETH_CHAIN_ID: DEFAULT_HTTP_LIMIT: 32768 DEFAULT_HTTP_TIMEOUT: 15s @@ -143,6 +148,7 @@ JOB_PIPELINE_REAPER_THRESHOLD: 24h0m0s KEEPER_DEFAULT_TRANSACTION_QUEUE_DEPTH: 1 KEEPER_GAS_PRICE_BUFFER_PERCENT: 20 KEEPER_GAS_TIP_CAP_BUFFER_PERCENT: 20 +KEEPER_BASE_FEE_BUFFER_PERCENT: 20 KEEPER_MAXIMUM_GRACE_PERIOD: 0 KEEPER_REGISTRY_CHECK_GAS_OVERHEAD: 0 KEEPER_REGISTRY_PERFORM_GAS_OVERHEAD: 0 @@ -156,7 +162,9 @@ LINK_CONTRACT_ADDRESS: LOG_FILE_DIR: %[1]s LOG_LEVEL: debug LOG_SQL: false -LOG_TO_DISK: false +LOG_FILE_MAX_SIZE: 5.12gb +LOG_FILE_MAX_AGE: 0 +LOG_FILE_MAX_BACKUPS: 1 TRIGGER_FALLBACK_DB_POLL_INTERVAL: 30s OCR_CONTRACT_TRANSMITTER_TRANSMIT_TIMEOUT: OCR_DATABASE_TIMEOUT: @@ -189,9 +197,10 @@ CHAINLINK_TLS_HOST: CHAINLINK_TLS_PORT: 6689 CHAINLINK_TLS_REDIRECT: false`, cfg.RootDir()) - if !strings.Contains(msg, expected) { - t.Errorf("Expected to find:\n\n%s\n\nWithin:\n\n%s\n\nDiff:\n\n%s", expected, msg, diff.Diff(expected, msg)) - } + logs, err := cltest.ReadLogs(lcfg.Dir) + assert.NoError(t, err) + + require.Contains(t, logs, expected, fmt.Sprintf("Expected to find:\n\n%s\n\nWithin:\n\n%s\n\nDiff:\n\n%s", expected, logs, diff.Diff(expected, logs))) app.AssertExpectations(t) } @@ -223,7 +232,7 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { app.On("SessionORM").Return(sessionORM) app.On("GetKeyStore").Return(keyStore) app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, cltest.NewEthClientMock(t), evmtest.NewChainScopedConfig(t, cfg))}).Maybe() - app.On("Start").Maybe().Return(nil) + app.On("Start", mock.Anything).Maybe().Return(nil) app.On("Stop").Maybe().Return(nil) app.On("ID").Maybe().Return(uuid.NewV4()) @@ -234,12 +243,15 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { cltest.MustInsertRandomKey(t, keyStore.Eth()) apiPrompt := cltest.NewMockAPIInitializer(t) + lggr := logger.TestLogger(t) + client := cmd.Client{ Config: cfg, - Logger: logger.TestLogger(t), - AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: apiPrompt, Runner: cltest.EmptyRunner{}, + AppFactory: cltest.InstanceAppFactory{App: app}, + Logger: lggr, + CloseLogger: lggr.Sync, } set := flag.NewFlagSet("test", 0) @@ -260,9 +272,10 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { func TestClient_RunNode_CreateFundingKeyIfNotExists(t *testing.T) { t.Parallel() + lggr := logger.TestLogger(t) cfg := cltest.NewTestGeneralConfig(t) db := pgtest.NewSqlxDB(t) - sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t)) + sessionORM := sessions.NewORM(db, time.Minute, lggr) keyStore := cltest.NewKeyStore(t, db, cfg) _, err := keyStore.Eth().Create(&cltest.FixtureChainID) require.NoError(t, err) @@ -271,7 +284,7 @@ func TestClient_RunNode_CreateFundingKeyIfNotExists(t *testing.T) { app.On("SessionORM").Return(sessionORM) app.On("GetKeyStore").Return(keyStore) app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, cltest.NewEthClientMock(t), evmtest.NewChainScopedConfig(t, cfg))}).Maybe() - app.On("Start").Maybe().Return(nil) + app.On("Start", mock.Anything).Maybe().Return(nil) app.On("Stop").Maybe().Return(nil) app.On("ID").Maybe().Return(uuid.NewV4()) @@ -282,12 +295,15 @@ func TestClient_RunNode_CreateFundingKeyIfNotExists(t *testing.T) { require.NoError(t, err) apiPrompt := cltest.NewMockAPIInitializer(t) + cLggr := logger.TestLogger(t) + client := cmd.Client{ Config: cfg, - Logger: logger.TestLogger(t), AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: apiPrompt, Runner: cltest.EmptyRunner{}, + Logger: cLggr, + CloseLogger: cLggr.Sync, } var keyState = ethkey.State{} @@ -337,7 +353,7 @@ func TestClient_RunNodeWithAPICredentialsFile(t *testing.T) { app.On("SessionORM").Return(sessionORM) app.On("GetKeyStore").Return(keyStore) app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, ethClient, evmtest.NewChainScopedConfig(t, cfg))}).Maybe() - app.On("Start").Maybe().Return(nil) + app.On("Start", mock.Anything).Maybe().Return(nil) app.On("Stop").Maybe().Return(nil) app.On("ID").Maybe().Return(uuid.NewV4()) @@ -345,18 +361,22 @@ func TestClient_RunNodeWithAPICredentialsFile(t *testing.T) { prompter.On("IsTerminal").Return(false).Once().Maybe() apiPrompt := cltest.NewMockAPIInitializer(t) + lggr := logger.TestLogger(t) + client := cmd.Client{ Config: cfg, - Logger: logger.TestLogger(t), AppFactory: cltest.InstanceAppFactory{App: app}, KeyStoreAuthenticator: cmd.TerminalKeyStoreAuthenticator{prompter}, FallbackAPIInitializer: apiPrompt, Runner: cltest.EmptyRunner{}, + Logger: lggr, + CloseLogger: lggr.Sync, } set := flag.NewFlagSet("test", 0) set.String("api", test.apiFile, "") set.String("password", "../internal/fixtures/correct_password.txt", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) if test.wantError { @@ -375,32 +395,45 @@ func TestClient_RunNodeWithAPICredentialsFile(t *testing.T) { } } -func TestClient_LogToDiskOptionDisablesAsExpected(t *testing.T) { +func TestClient_DiskMaxSizeBeforeRotateOptionDisablesAsExpected(t *testing.T) { tests := []struct { name string - logToDiskValue bool + logFileSize func(t *testing.T) utils.FileSize fileShouldExist bool }{ - {"LogToDisk = false => no log on disk", false, false}, - {"LogToDisk = true => log on disk (positive control)", true, true}, + {"DiskMaxSizeBeforeRotate = 0 => no log on disk", func(t *testing.T) utils.FileSize { + return 0 + }, false}, + {"DiskMaxSizeBeforeRotate > 0 => log on disk (positive control)", func(t *testing.T) utils.FileSize { + var logFileSize utils.FileSize + err := logFileSize.UnmarshalText([]byte("100mb")) + assert.NoError(t, err) + + return logFileSize + }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := logger.Config{ - ToDisk: tt.logToDiskValue, - Dir: t.TempDir(), + Dir: t.TempDir(), + FileMaxSize: int(tt.logFileSize(t)), } - require.NoError(t, os.MkdirAll(cfg.Dir, os.FileMode(0700))) + assert.NoError(t, os.MkdirAll(cfg.Dir, os.FileMode(0700))) - cfg.New().Sync() - filepath := filepath.Join(cfg.Dir, "log.jsonl") + lggr, close := cfg.New() + defer close() + + // Tries to create a log file by logging. The log file won't be created if there's no logging happening. + lggr.Debug("Trying to create a log file by logging.") + + filepath := filepath.Join(cfg.Dir, logger.LogsFile) _, err := os.Stat(filepath) - assert.Equal(t, os.IsNotExist(err), !tt.fileShouldExist) + require.Equal(t, os.IsNotExist(err), !tt.fileShouldExist) }) } } -func TestClient_RebroadcastTransactions_BPTXM(t *testing.T) { +func TestClient_RebroadcastTransactions_Txm(t *testing.T) { // Use the a non-transactional db for this test because we need to // test multiple connections to the database, and changes made within // the transaction cannot be seen from another connection. @@ -422,7 +455,7 @@ func TestClient_RebroadcastTransactions_BPTXM(t *testing.T) { set.String("password", "../internal/fixtures/correct_password.txt", "") c := cli.NewContext(nil, set, nil) - borm := cltest.NewBulletproofTxManagerORM(t, sqlxDB, config) + borm := cltest.NewTxmORM(t, sqlxDB, config) cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, borm, 7, 42, fromAddress) app := new(mocks.Application) @@ -435,12 +468,15 @@ func TestClient_RebroadcastTransactions_BPTXM(t *testing.T) { app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, ethClient, evmtest.NewChainScopedConfig(t, config))}).Maybe() ethClient.On("Dial", mock.Anything).Return(nil) + lggr := logger.TestLogger(t) + client := cmd.Client{ Config: config, - Logger: logger.TestLogger(t), AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), Runner: cltest.EmptyRunner{}, + Logger: lggr, + CloseLogger: lggr.Sync, } config.Overrides.Dialect = dialects.TransactionWrappedPostgres @@ -458,7 +494,7 @@ func TestClient_RebroadcastTransactions_BPTXM(t *testing.T) { ethClient.AssertExpectations(t) } -func TestClient_RebroadcastTransactions_OutsideRange_BPTXM(t *testing.T) { +func TestClient_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { beginningNonce := uint(7) endingNonce := uint(10) gasPrice := big.NewInt(100000000000) @@ -494,7 +530,7 @@ func TestClient_RebroadcastTransactions_OutsideRange_BPTXM(t *testing.T) { set.String("password", "../internal/fixtures/correct_password.txt", "") c := cli.NewContext(nil, set, nil) - borm := cltest.NewBulletproofTxManagerORM(t, sqlxDB, config) + borm := cltest.NewTxmORM(t, sqlxDB, config) cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, borm, int64(test.nonce), 42, fromAddress) app := new(mocks.Application) @@ -507,12 +543,15 @@ func TestClient_RebroadcastTransactions_OutsideRange_BPTXM(t *testing.T) { ethClient.On("Dial", mock.Anything).Return(nil) app.On("GetChains").Return(chainlink.Chains{EVM: cltest.NewChainSetMockWithOneChain(t, ethClient, evmtest.NewChainScopedConfig(t, config))}).Maybe() + lggr := logger.TestLogger(t) + client := cmd.Client{ Config: config, - Logger: logger.TestLogger(t), AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), Runner: cltest.EmptyRunner{}, + Logger: lggr, + CloseLogger: lggr.Sync, } config.Overrides.Dialect = dialects.TransactionWrappedPostgres @@ -537,11 +576,13 @@ func TestClient_SetNextNonce(t *testing.T) { // Need to use separate database config, sqlxDB := heavyweight.FullTestDB(t, "setnextnonce", true, true) ethKeyStore := cltest.NewKeyStore(t, sqlxDB, config).Eth() + lggr := logger.TestLogger(t) client := cmd.Client{ - Config: config, - Logger: logger.TestLogger(t), - Runner: cltest.EmptyRunner{}, + Config: config, + Runner: cltest.EmptyRunner{}, + Logger: lggr, + CloseLogger: lggr.Sync, } _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore, 0) diff --git a/core/cmd/remote_client.go b/core/cmd/remote_client.go index 643947b69be..b7236a0d51a 100644 --- a/core/cmd/remote_client.go +++ b/core/cmd/remote_client.go @@ -17,7 +17,7 @@ import ( "time" "github.com/manyminds/api2go/jsonapi" - homedir "github.com/mitchellh/go-homedir" + "github.com/mitchellh/go-homedir" "github.com/pelletier/go-toml" "github.com/pkg/errors" "github.com/tidwall/gjson" @@ -26,7 +26,9 @@ import ( "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/config" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/sessions" + "github.com/smartcontractkit/chainlink/core/static" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" @@ -125,15 +127,20 @@ func (cli *Client) getPage(requestURI string, page int, model interface{}) (err // ReplayFromBlock replays chain data from the given block number until the most recent func (cli *Client) ReplayFromBlock(c *clipkg.Context) (err error) { - blockNumber := c.Int64("block-number") if blockNumber <= 0 { return cli.errorOut(errors.New("Must pass a positive value in '--block-number' parameter")) } - buf := bytes.NewBufferString("{}") + forceBroadcast := c.Bool("force") - resp, err := cli.HTTP.Post(fmt.Sprintf("/v2/replay_from_block/%v", blockNumber), buf) + buf := bytes.NewBufferString("{}") + resp, err := cli.HTTP.Post( + fmt.Sprintf( + "/v2/replay_from_block/%v?force=%s", + blockNumber, + strconv.FormatBool(forceBroadcast), + ), buf) if err != nil { return cli.errorOut(err) } @@ -158,12 +165,21 @@ func (cli *Client) ReplayFromBlock(c *clipkg.Context) (err error) { // RemoteLogin creates a cookie session to run remote commands. func (cli *Client) RemoteLogin(c *clipkg.Context) error { + lggr := cli.Logger.Named("RemoteLogin") sessionRequest, err := cli.buildSessionRequest(c.String("file")) if err != nil { return cli.errorOut(err) } _, err = cli.CookieAuthenticator.Authenticate(sessionRequest) - return cli.errorOut(err) + if err != nil { + return cli.errorOut(err) + } + err = cli.checkRemoteBuildCompatibility(lggr, c.Bool("bypass-version-check"), static.Version, static.Sha) + if err != nil { + return cli.errorOut(err) + } + fmt.Println("Successfully Logged In.") + return nil } // ChangePassword prompts the user for the old password and a new one, then @@ -293,7 +309,7 @@ func getTOMLString(s string) (string, error) { func (cli *Client) parseResponse(resp *http.Response) ([]byte, error) { b, err := parseResponse(resp) - if err == errUnauthorized { + if errors.Is(err, errUnauthorized) { return nil, cli.errorOut(multierr.Append(err, fmt.Errorf("your credentials may be missing, invalid or you may need to login first using the CLI via 'chainlink admin login'"))) } if err != nil { @@ -542,3 +558,48 @@ func parseResponse(resp *http.Response) ([]byte, error) { } return b, err } + +func (cli *Client) checkRemoteBuildCompatibility(lggr logger.Logger, onlyWarn bool, cliVersion, cliSha string) error { + resp, err := cli.HTTP.Get("/v2/build_info") + if err != nil { + lggr.Warnw("Got error querying for version. Remote node version is unknown and CLI may behave in unexpected ways.", "err", err) + return nil + } + b, err := parseResponse(resp) + if err != nil { + lggr.Warnw("Got error parsing http response for remote version. Remote node version is unknown and CLI may behave in unexpected ways.", "resp", resp, "err", err) + return nil + } + + var remoteBuildInfo map[string]string + if err := json.Unmarshal(b, &remoteBuildInfo); err != nil { + lggr.Warnw("Got error json parsing bytes from remote version response. Remote node version is unknown and CLI may behave in unexpected ways.", "bytes", b, "err", err) + return nil + } + remoteVersion, remoteSha := remoteBuildInfo["version"], remoteBuildInfo["commitSHA"] + + remoteSemverUnset := remoteVersion == "unset" || remoteVersion == "" || remoteSha == "unset" || remoteSha == "" + cliRemoteSemverMismatch := remoteVersion != cliVersion || remoteSha != cliSha + + if remoteSemverUnset || cliRemoteSemverMismatch { + // Show a warning but allow mismatch + if onlyWarn { + lggr.Warnf("CLI build (%s@%s) mismatches remote node build (%s@%s), it might behave in unexpected ways", remoteVersion, remoteSha, cliVersion, cliSha) + return nil + } + // Don't allow usage of CLI by unsetting the session cookie to prevent further requests + cli.CookieAuthenticator.Logout() + return ErrIncompatible{CLIVersion: cliVersion, CLISha: cliSha, RemoteVersion: remoteVersion, RemoteSha: remoteSha} + } + return nil +} + +// ErrIncompatible is returned when the cli and remote versions are not compatible. +type ErrIncompatible struct { + CLIVersion, CLISha string + RemoteVersion, RemoteSha string +} + +func (e ErrIncompatible) Error() string { + return fmt.Sprintf("error: CLI build (%s@%s) mismatches remote node build (%s@%s). You can set flag --bypass-version-check to bypass this", e.CLIVersion, e.CLISha, e.RemoteVersion, e.RemoteSha) +} diff --git a/core/cmd/remote_client_test.go b/core/cmd/remote_client_test.go index 69d5d572724..5c81dbb7410 100644 --- a/core/cmd/remote_client_test.go +++ b/core/cmd/remote_client_test.go @@ -1,10 +1,13 @@ package cmd_test import ( + "bytes" "context" "errors" "flag" "fmt" + "io" + "io/ioutil" "math/big" "net/http" "os" @@ -24,11 +27,13 @@ import ( "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/sessions" + "github.com/smartcontractkit/chainlink/core/static" "github.com/smartcontractkit/chainlink/core/testdata/testspecs" "github.com/smartcontractkit/chainlink/core/web" ) @@ -67,13 +72,14 @@ func startNewApplication(t *testing.T, setup ...func(opts *startOptions)) *cltes // your tests, you can manually override and turn it on using // withConfigSet. config.Overrides.EVMEnabled = null.BoolFrom(false) + config.Overrides.P2PEnabled = null.BoolFrom(false) if sopts.SetConfig != nil { sopts.SetConfig(config) } app := cltest.NewApplicationWithConfigAndKey(t, config, sopts.FlagsAndDeps...) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) return app } @@ -97,20 +103,15 @@ func withKey() func(opts *startOptions) { } } -func newEthMock(t *testing.T) (*evmmocks.Client, func()) { +func newEthMock(t *testing.T) *evmmocks.Client { t.Helper() - - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - - return ethClient, assertMocksCalled + return cltest.NewEthMocksWithStartupAssertions(t) } -func newEthMockWithTransactionsOnBlocksAssertions(t *testing.T) (*evmmocks.Client, func()) { +func newEthMockWithTransactionsOnBlocksAssertions(t *testing.T) *evmmocks.Client { t.Helper() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - - return ethClient, assertMocksCalled + return cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) } func keyNameForTest(t *testing.T) string { @@ -268,11 +269,12 @@ func TestClient_RemoteLogin(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { enteredStrings := []string{test.email, test.pwd} - prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingClient(prompter) set := flag.NewFlagSet("test", 0) set.String("file", test.file, "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) @@ -285,19 +287,129 @@ func TestClient_RemoteLogin(t *testing.T) { } } +func TestClient_RemoteBuildCompatibility(t *testing.T) { + t.Parallel() + + app := startNewApplication(t) + enteredStrings := []string{cltest.APIEmail, cltest.Password} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: append(enteredStrings, enteredStrings...)} + client := app.NewAuthenticatingClient(prompter) + + remoteVersion, remoteSha := "test"+static.Version, "abcd"+static.Sha + client.HTTP = &mockHTTPClient{client.HTTP, remoteVersion, remoteSha} + + expErr := cmd.ErrIncompatible{ + CLIVersion: static.Version, + CLISha: static.Sha, + RemoteVersion: remoteVersion, + RemoteSha: remoteSha, + }.Error() + + // Fails without bypass + set := flag.NewFlagSet("test", 0) + set.Bool("bypass-version-check", false, "") + c := cli.NewContext(nil, set, nil) + err := client.RemoteLogin(c) + assert.Error(t, err) + assert.EqualError(t, err, expErr) + + // Defaults to false + set = flag.NewFlagSet("test", 0) + c = cli.NewContext(nil, set, nil) + err = client.RemoteLogin(c) + assert.Error(t, err) + assert.EqualError(t, err, expErr) +} + +func TestClient_CheckRemoteBuildCompatibility(t *testing.T) { + t.Parallel() + + app := startNewApplication(t) + tests := []struct { + name string + remoteVersion, remoteSha string + cliVersion, cliSha string + bypassVersionFlag, wantError bool + }{ + {"success match", "1.1.1", "53120d5", "1.1.1", "53120d5", false, false}, + {"cli unset fails", "1.1.1", "53120d5", "unset", "unset", false, true}, + {"remote unset fails", "unset", "unset", "1.1.1", "53120d5", false, true}, + {"mismatch fail", "1.1.1", "53120d5", "1.6.9", "13230sas", false, true}, + {"mismatch but using bypass_version_flag", "1.1.1", "53120d5", "1.6.9", "13230sas", true, false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + enteredStrings := []string{cltest.APIEmail, cltest.Password} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} + client := app.NewAuthenticatingClient(prompter) + + client.HTTP = &mockHTTPClient{client.HTTP, test.remoteVersion, test.remoteSha} + + err := client.CheckRemoteBuildCompatibility(logger.TestLogger(t), test.bypassVersionFlag, test.cliVersion, test.cliSha) + if test.wantError { + assert.Error(t, err) + assert.ErrorIs(t, err, cmd.ErrIncompatible{ + RemoteVersion: test.remoteVersion, + RemoteSha: test.remoteSha, + CLIVersion: test.cliVersion, + CLISha: test.cliSha, + }) + } else { + assert.NoError(t, err) + } + }) + } +} + +type mockHTTPClient struct { + HTTP cmd.HTTPClient + mockVersion string + mockSha string +} + +func (h *mockHTTPClient) Get(path string, headers ...map[string]string) (*http.Response, error) { + if path == "/v2/build_info" { + // Return mocked response here + json := fmt.Sprintf(`{"version":"%s","commitSHA":"%s"}`, h.mockVersion, h.mockSha) + r := ioutil.NopCloser(bytes.NewReader([]byte(json))) + return &http.Response{ + StatusCode: 200, + Body: r, + }, nil + } + return h.HTTP.Get(path, headers...) +} + +func (h *mockHTTPClient) Post(path string, body io.Reader) (*http.Response, error) { + return h.HTTP.Post(path, body) +} + +func (h *mockHTTPClient) Put(path string, body io.Reader) (*http.Response, error) { + return h.HTTP.Put(path, body) +} + +func (h *mockHTTPClient) Patch(path string, body io.Reader, headers ...map[string]string) (*http.Response, error) { + return h.HTTP.Patch(path, body, headers...) +} + +func (h *mockHTTPClient) Delete(path string) (*http.Response, error) { + return h.HTTP.Delete(path) +} + func TestClient_ChangePassword(t *testing.T) { t.Parallel() app := startNewApplication(t) enteredStrings := []string{cltest.APIEmail, cltest.Password} - prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingClient(prompter) otherClient := app.NewAuthenticatingClient(prompter) set := flag.NewFlagSet("test", 0) set.String("file", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) require.NoError(t, err) @@ -325,12 +437,13 @@ func TestClient_Profile_InvalidSecondsParam(t *testing.T) { app := startNewApplication(t) enteredStrings := []string{cltest.APIEmail, cltest.Password} - prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingClient(prompter) set := flag.NewFlagSet("test", 0) set.String("file", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) require.NoError(t, err) @@ -348,12 +461,13 @@ func TestClient_Profile(t *testing.T) { app := startNewApplication(t) enteredStrings := []string{cltest.APIEmail, cltest.Password} - prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} + prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingClient(prompter) set := flag.NewFlagSet("test", 0) set.String("file", "../internal/fixtures/apicredentials", "") + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) err := client.RemoteLogin(c) require.NoError(t, err) @@ -367,8 +481,7 @@ func TestClient_Profile(t *testing.T) { func TestClient_SetDefaultGasPrice(t *testing.T) { t.Parallel() - ethMock, assertMocksCalled := newEthMock(t) - defer assertMocksCalled() + ethMock := newEthMock(t) app := startNewApplication(t, withKey(), withMocks(ethMock), @@ -476,17 +589,18 @@ func TestClient_RunOCRJob_HappyPath(t *testing.T) { ocrspec := testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{DS1BridgeName: bridge.Name.String(), DS2BridgeName: bridge2.Name.String()}) err := toml.Unmarshal([]byte(ocrspec.Toml()), &jb) require.NoError(t, err) - var ocrSpec job.OffchainReportingOracleSpec + var ocrSpec job.OCROracleSpec err = toml.Unmarshal([]byte(ocrspec.Toml()), &ocrspec) require.NoError(t, err) - jb.OffchainreportingOracleSpec = &ocrSpec + jb.OCROracleSpec = &ocrSpec key, _ := cltest.MustInsertRandomKey(t, app.KeyStore.Eth()) - jb.OffchainreportingOracleSpec.TransmitterAddress = &key.Address + jb.OCROracleSpec.TransmitterAddress = &key.Address err = app.AddJobV2(context.Background(), &jb) require.NoError(t, err) set := flag.NewFlagSet("test", 0) + set.Bool("bypass-version-check", true, "") set.Parse([]string{strconv.FormatInt(int64(jb.ID), 10)}) c := cli.NewContext(nil, set, nil) @@ -501,6 +615,7 @@ func TestClient_RunOCRJob_MissingJobID(t *testing.T) { client, _ := app.NewClientAndRenderer() set := flag.NewFlagSet("test", 0) + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) require.NoError(t, client.RemoteLogin(c)) @@ -515,6 +630,7 @@ func TestClient_RunOCRJob_JobNotFound(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Parse([]string{"1"}) + set.Bool("bypass-version-check", true, "") c := cli.NewContext(nil, set, nil) require.NoError(t, client.RemoteLogin(c)) @@ -580,6 +696,11 @@ func (FailingAuthenticator) Authenticate(sessionRequest sessions.SessionRequest) return nil, errors.New("no luck") } +// Remove a session ID from disk +func (FailingAuthenticator) Logout() error { + return errors.New("no luck") +} + func TestClient_SetLogConfig(t *testing.T) { t.Parallel() diff --git a/core/cmd/renderer_test.go b/core/cmd/renderer_test.go index 53cf6de2922..3f98e050a53 100644 --- a/core/cmd/renderer_test.go +++ b/core/cmd/renderer_test.go @@ -9,8 +9,10 @@ import ( "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/web" webpresenters "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -35,7 +37,7 @@ func TestRendererTable_RenderConfiguration(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/config") diff --git a/core/cmd/terra_chains_commands_test.go b/core/cmd/terra_chains_commands_test.go index eed5824be51..524a4dc7801 100644 --- a/core/cmd/terra_chains_commands_test.go +++ b/core/cmd/terra_chains_commands_test.go @@ -2,38 +2,33 @@ package cmd_test import ( "flag" - "fmt" - "math/rand" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" - null "gopkg.in/guregu/null.v4" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/terratest" "github.com/smartcontractkit/chainlink/core/store/models" ) -func randTerraChainID() string { - return fmt.Sprintf("Chainlinktest-%d", rand.Int31n(999999)) -} - func TestClient_IndexTerraChains(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - chain, err := orm.CreateChain(randTerraChainID(), db.ChainCfg{}) + chain, err := orm.CreateChain(terratest.RandomChainID(), db.ChainCfg{}) require.NoError(t, err) require.Nil(t, client.IndexTerraChains(cltest.EmptyCLIContext())) @@ -47,14 +42,14 @@ func TestClient_IndexTerraChains(t *testing.T) { func TestClient_CreateTerraChain(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - terraChainID := randTerraChainID() + terraChainID := terratest.RandomChainID() set := flag.NewFlagSet("cli", 0) set.String("id", terraChainID, "") set.Parse([]string{`{}`}) @@ -74,14 +69,14 @@ func TestClient_CreateTerraChain(t *testing.T) { func TestClient_RemoveTerraChain(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - terraChainID := randTerraChainID() + terraChainID := terratest.RandomChainID() _, err = orm.CreateChain(terraChainID, db.ChainCfg{}) require.NoError(t, err) chains, _, err := orm.Chains(0, 25) @@ -104,15 +99,15 @@ func TestClient_RemoveTerraChain(t *testing.T) { func TestClient_ConfigureTerraChain(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Chains(0, 25) require.NoError(t, err) - terraChainID := randTerraChainID() + terraChainID := terratest.RandomChainID() minute := models.MustMakeDuration(time.Minute) original := db.ChainCfg{ FallbackGasPriceULuna: null.StringFrom("99.07"), diff --git a/core/cmd/terra_commands_test.go b/core/cmd/terra_commands_test.go index 486a4ed1db7..f99f90a8959 100644 --- a/core/cmd/terra_commands_test.go +++ b/core/cmd/terra_commands_test.go @@ -13,7 +13,7 @@ import ( func TestClient_TerraInit(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() newNode := types.NewNode{ diff --git a/core/cmd/terra_node_commands_test.go b/core/cmd/terra_node_commands_test.go index d71f9aac05d..cfa82ee620c 100644 --- a/core/cmd/terra_node_commands_test.go +++ b/core/cmd/terra_node_commands_test.go @@ -10,12 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" "github.com/smartcontractkit/chainlink/core/chains/terra/types" "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" ) func mustInsertTerraChain(t *testing.T, orm types.ORM, id string) db.Chain { @@ -24,13 +26,21 @@ func mustInsertTerraChain(t *testing.T, orm types.ORM, id string) db.Chain { return chain } +func terraStartNewApplication(t *testing.T) *cltest.TestApplication { + return startNewApplication(t, withConfigSet(func(c *configtest.TestGeneralConfig) { + c.Overrides.TerraEnabled = null.BoolFrom(true) + c.Overrides.EVMEnabled = null.BoolFrom(false) + c.Overrides.EVMRPCEnabled = null.BoolFrom(false) + })) +} + func TestClient_IndexTerraNodes(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Nodes(0, 25) require.NoError(t, err) chainID := fmt.Sprintf("Chainlinktest-%d", rand.Int31n(999999)) @@ -59,10 +69,10 @@ func TestClient_IndexTerraNodes(t *testing.T) { func TestClient_CreateTerraNode(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialNodesCount, err := orm.Nodes(0, 25) require.NoError(t, err) chainIDA := fmt.Sprintf("Chainlinktest-%d", rand.Int31n(999999)) @@ -110,10 +120,10 @@ func TestClient_CreateTerraNode(t *testing.T) { func TestClient_RemoveTerraNode(t *testing.T) { t.Parallel() - app := startNewApplication(t) + app := terraStartNewApplication(t) client, r := app.NewClientAndRenderer() - orm := app.TerraORM() + orm := app.Chains.Terra.ORM() _, initialCount, err := orm.Nodes(0, 25) require.NoError(t, err) chainID := fmt.Sprintf("Chainlinktest-%d", rand.Int31n(999999)) diff --git a/core/cmd/terra_transaction_commands.go b/core/cmd/terra_transaction_commands.go new file mode 100644 index 00000000000..f0a194c6bf8 --- /dev/null +++ b/core/cmd/terra_transaction_commands.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/urfave/cli" + "go.uber.org/multierr" + + "github.com/smartcontractkit/chainlink/core/store/models/terra" + "github.com/smartcontractkit/chainlink/core/web/presenters" +) + +type TerraMsgPresenter struct { + JAID + presenters.TerraMsgResource +} + +// RenderTable implements TableRenderer +func (p *TerraMsgPresenter) RenderTable(rt RendererTable) error { + table := rt.newTable([]string{"Chain ID", "Contract ID", "State", "Tx Hash"}) + var hash string + if p.TxHash != nil { + hash = *p.TxHash + } + table.Append([]string{ + p.ChainID, + p.ContractID, + p.State, + hash, + }) + + render(fmt.Sprintf("Terra Message %v", p.ID), table) + return nil +} + +// TerraSendLuna transfers coins from the node's account to a specified address. +func (cli *Client) TerraSendLuna(c *cli.Context) (err error) { + if c.NArg() < 3 { + return cli.errorOut(errors.New("three arguments expected: amount, fromAddress and toAddress")) + } + + amount, err := sdk.NewDecFromStr(c.Args().Get(0)) + if err != nil { + return cli.errorOut(fmt.Errorf("invalid coin: %w", err)) + } + + unparsedFromAddress := c.Args().Get(1) + fromAddress, err := sdk.AccAddressFromBech32(unparsedFromAddress) + if err != nil { + return cli.errorOut(multierr.Combine( + fmt.Errorf("while parsing withdrawal source address %v", + unparsedFromAddress), err)) + } + + unparsedDestinationAddress := c.Args().Get(2) + destinationAddress, err := sdk.AccAddressFromBech32(unparsedDestinationAddress) + if err != nil { + return cli.errorOut(multierr.Combine( + fmt.Errorf("while parsing withdrawal destination address %v", + unparsedDestinationAddress), err)) + } + + chainID := c.String("id") + if chainID == "" { + return cli.errorOut(errors.New("missing id")) + } + + request := terra.SendRequest{ + DestinationAddress: destinationAddress, + FromAddress: fromAddress, + Amount: amount, + TerraChainID: chainID, + AllowHigherAmounts: c.IsSet("force"), + } + + requestData, err := json.Marshal(request) + if err != nil { + return cli.errorOut(err) + } + + buf := bytes.NewBuffer(requestData) + + resp, err := cli.HTTP.Post("/v2/transfers/terra", buf) + if err != nil { + return cli.errorOut(err) + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + err = multierr.Append(err, cerr) + } + }() + + err = cli.renderAPIResponse(resp, &TerraMsgPresenter{}) + return err +} diff --git a/core/cmd/terra_transaction_commands_test.go b/core/cmd/terra_transaction_commands_test.go new file mode 100644 index 00000000000..b23b966bb3b --- /dev/null +++ b/core/cmd/terra_transaction_commands_test.go @@ -0,0 +1,151 @@ +//go:build integration + +package cmd_test + +import ( + "flag" + "strconv" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" + + terraclient "github.com/smartcontractkit/chainlink-terra/pkg/terra/client" + terradb "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" + + "github.com/smartcontractkit/chainlink/core/chains/terra/denom" + "github.com/smartcontractkit/chainlink/core/chains/terra/terratxm" + terratypes "github.com/smartcontractkit/chainlink/core/chains/terra/types" + "github.com/smartcontractkit/chainlink/core/cmd" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/terratest" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/terrakey" +) + +func TestClient_SendTerraCoins(t *testing.T) { + chainID := terratest.RandomChainID() + accounts, _, tendermintURL := terraclient.SetupLocalTerraNode(t, chainID) + require.Greater(t, len(accounts), 1) + app := terraStartNewApplication(t) + + from := accounts[0] + to := accounts[1] + require.NoError(t, app.GetKeyStore().Terra().Add(terrakey.Raw(from.PrivateKey.Bytes()).Key())) + + chains := app.GetChains() + _, err := chains.Terra.Add(testutils.Context(t), chainID, terradb.ChainCfg{}) + require.NoError(t, err) + chain, err := chains.Terra.Chain(testutils.Context(t), chainID) + require.NoError(t, err) + + _, err = chains.Terra.ORM().CreateNode(terratypes.NewNode{ + Name: t.Name(), + TerraChainID: chainID, + TendermintURL: tendermintURL, + }) + require.NoError(t, err) + + reader, err := chain.Reader("") + require.NoError(t, err) + + require.Eventually(t, func() bool { + coin, err := reader.Balance(from.Address, "uluna") + if !assert.NoError(t, err) { + return false + } + return coin.IsPositive() + }, time.Minute, 5*time.Second) + + db := app.GetSqlxDB() + orm := terratxm.NewORM(chainID, db, logger.TestLogger(t), pgtest.NewPGCfg(true)) + + client, r := app.NewClientAndRenderer() + cliapp := cli.NewApp() + + for _, tt := range []struct { + amount string + expErr string + }{ + {amount: "0.000001"}, + {amount: "1"}, + {amount: "30.000001"}, + {amount: "1000", expErr: "is too low for this transaction to be executed:"}, + {amount: "0", expErr: "amount must be greater than zero:"}, + {amount: "asdf", expErr: "invalid coin: failed to set decimal string:"}, + } { + tt := tt + t.Run(tt.amount, func(t *testing.T) { + startBal, err := reader.Balance(from.Address, "uluna") + require.NoError(t, err) + + set := flag.NewFlagSet("sendterracoins", 0) + set.String("id", chainID, "") + set.Parse([]string{tt.amount, from.Address.String(), to.Address.String()}) + c := cli.NewContext(cliapp, set, nil) + err = client.TerraSendLuna(c) + if tt.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expErr) + return + } + + // Check CLI output + require.Greater(t, len(r.Renders), 0) + renderer := r.Renders[len(r.Renders)-1] + renderedMsg := renderer.(*cmd.TerraMsgPresenter) + require.NotEmpty(t, renderedMsg.ID) + assert.Equal(t, string(terradb.Unstarted), renderedMsg.State) + assert.Nil(t, renderedMsg.TxHash) + id, err := strconv.ParseInt(renderedMsg.ID, 10, 64) + require.NoError(t, err) + msgs, err := orm.GetMsgs(id) + require.NoError(t, err) + require.Equal(t, 1, len(msgs)) + msg := msgs[0] + assert.Equal(t, strconv.FormatInt(msg.ID, 10), renderedMsg.ID) + assert.Equal(t, msg.ChainID, renderedMsg.ChainID) + assert.Equal(t, msg.ContractID, renderedMsg.ContractID) + require.NotEqual(t, terradb.Errored, msg.State) + switch msg.State { + case terradb.Unstarted: + assert.Nil(t, msg.TxHash) + case terradb.Broadcasted, terradb.Confirmed: + assert.NotNil(t, msg.TxHash) + } + + // Maybe wait for confirmation + if msg.State != terradb.Confirmed { + require.Eventually(t, func() bool { + msgs, err := orm.GetMsgs(id) + if assert.NoError(t, err) && assert.NotEmpty(t, msgs) { + if msg = msgs[0]; assert.Equal(t, msg.ID, id) { + t.Log("State:", msg.State) + return msg.State == terradb.Confirmed + } + } + return false + }, testutils.WaitTimeout(t), time.Second) + require.NotNil(t, msg.TxHash) + } + + // Check balance + endBal, err := reader.Balance(from.Address, "uluna") + require.NoError(t, err) + if assert.NotNil(t, startBal) && assert.NotNil(t, endBal) { + diff := startBal.Sub(*endBal).Amount + sent, err := denom.ConvertToULuna(sdk.NewDecCoinFromDec("luna", sdk.MustNewDecFromStr(tt.amount))) + require.NoError(t, err) + if assert.True(t, diff.IsInt64()) && assert.True(t, sent.Amount.IsInt64()) { + require.Greater(t, diff.Int64(), sent.Amount.Int64()) + } + } + }) + } +} diff --git a/core/config/envvar/envvar.go b/core/config/envvar/envvar.go index c7bb075a18e..4d494293d35 100644 --- a/core/config/envvar/envvar.go +++ b/core/config/envvar/envvar.go @@ -9,14 +9,24 @@ import ( "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/core/config/parse" + "github.com/smartcontractkit/chainlink/core/utils" ) var ( - LogLevel = New("LogLevel", parse.LogLevel) - RootDir = New("RootDir", parse.HomeDir) + // LogLevel reprents a parseable version of the `LOG_LEVEL`env var. + LogLevel = New("LogLevel", parse.LogLevel) + // RootDir reprents a parseable version of the `ROOT`env var. + RootDir = New("RootDir", parse.HomeDir) + // JSONConsole reprents a parseable version of the `JSON_CONSOLE`env var. JSONConsole = New("JSONConsole", parse.Bool) - LogToDisk = New("LogToDisk", parse.Bool) - LogUnixTS = New("LogUnixTS", parse.Bool) + // LogFileMaxSize reprents a parseable version of the `LOG_FILE_MAX_SIZE`env var. + LogFileMaxSize = New("LogFileMaxSize", parse.FileSize) + // LogFileMaxAge reprents a parseable version of the `LOG_FILE_MAX_AGE`env var. + LogFileMaxAge = New("LogFileMaxAge", parse.Int64) + // LogFileMaxBackups reprents a parseable version of the `LOG_FILE_MAX_BACKUPS`env var. + LogFileMaxBackups = New("LogFileMaxBackups", parse.Int64) + // LogUnixTS reprents a parseable version of the `LOG_UNIX_TS`env var. + LogUnixTS = New("LogUnixTS", parse.Bool) ) // EnvVar is an environment variable which @@ -87,6 +97,20 @@ func (e *EnvVar) ParseBool() (v bool, invalid string) { return i.(bool), invalid } +// ParseInt64 parses value into `int64` +func (e *EnvVar) ParseInt64() (v int64, invalid string) { + var i interface{} + i, invalid = e.Parse() + return i.(int64), invalid +} + +// ParseFileSize parses value into `utils.FileSize` +func (e *EnvVar) ParseFileSize() (v utils.FileSize, invalid string) { + var i interface{} + i, invalid = e.Parse() + return i.(utils.FileSize), invalid +} + // ParseLogLevel parses an env var's log level func (e *EnvVar) ParseLogLevel() (v zapcore.Level, invalid string) { var i interface{} diff --git a/core/config/envvar/schema.go b/core/config/envvar/schema.go index 3b2de4d721c..7f573077fb5 100644 --- a/core/config/envvar/schema.go +++ b/core/config/envvar/schema.go @@ -9,16 +9,31 @@ import ( "reflect" "time" - "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" ocrnetworking "github.com/smartcontractkit/libocr/networking" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" ) // ConfigSchema records the schema of configuration at the type level +// +// A note on Feature Flags +// +// Feature flags should be used during development of large features that might +// span more than one release cycle. Most changes that are not considered "complete" +// when a PR is merged and might affect node operation should be put behind a +// feature flag. +// +// This also allows to disable large parts of the code that may not be needed +// for all deployments that could introduce attack surface area if it is not +// needed. +// +// Good example usage is for alternative blockchain support, new services like +// Feeds Manager, external initiators and so on. type ConfigSchema struct { // ESSENTIAL DatabaseURL string `env:"DATABASE_URL"` @@ -33,12 +48,14 @@ type ConfigSchema struct { InsecureFastScrypt bool `env:"INSECURE_FAST_SCRYPT" default:"false"` //nodoc ReaperExpiration models.Duration `env:"REAPER_EXPIRATION" default:"240h"` //nodoc RootDir string `env:"ROOT" default:"~/.chainlink"` + TelemetryIngressUniConn bool `env:"TELEMETRY_INGRESS_UNICONN" default:"true"` TelemetryIngressLogging bool `env:"TELEMETRY_INGRESS_LOGGING" default:"false"` TelemetryIngressServerPubKey string `env:"TELEMETRY_INGRESS_SERVER_PUB_KEY"` TelemetryIngressURL *url.URL `env:"TELEMETRY_INGRESS_URL"` TelemetryIngressBufferSize uint `env:"TELEMETRY_INGRESS_BUFFER_SIZE" default:"100"` TelemetryIngressMaxBatchSize uint `env:"TELEMETRY_INGRESS_MAX_BATCH_SIZE" default:"50"` TelemetryIngressSendInterval time.Duration `env:"TELEMETRY_INGRESS_SEND_INTERVAL" default:"500ms"` + TelemetryIngressSendTimeout time.Duration `env:"TELEMETRY_INGRESS_SEND_TIMEOUT" default:"10s"` TelemetryIngressUseBatchSend bool `env:"TELEMETRY_INGRESS_USE_BATCH_SEND" default:"true"` ShutdownGracePeriod time.Duration `env:"SHUTDOWN_GRACE_PERIOD" default:"5s"` @@ -52,7 +69,7 @@ type ConfigSchema struct { // Database Global Lock AdvisoryLockCheckInterval time.Duration `env:"ADVISORY_LOCK_CHECK_INTERVAL" default:"1s"` AdvisoryLockID int64 `env:"ADVISORY_LOCK_ID" default:"1027321974924625846"` - DatabaseLockingMode string `env:"DATABASE_LOCKING_MODE" default:"advisorylock"` + DatabaseLockingMode string `env:"DATABASE_LOCKING_MODE" default:"dual"` LeaseLockDuration time.Duration `env:"LEASE_LOCK_DURATION" default:"10s"` LeaseLockRefreshInterval time.Duration `env:"LEASE_LOCK_REFRESH_INTERVAL" default:"1s"` // Database Autobackups @@ -63,12 +80,14 @@ type ConfigSchema struct { DatabaseBackupURL *url.URL `env:"DATABASE_BACKUP_URL"` // Logging - JSONConsole bool `env:"JSON_CONSOLE" default:"false"` - LogFileDir string `env:"LOG_FILE_DIR"` - LogLevel zapcore.Level `env:"LOG_LEVEL"` - LogSQL bool `env:"LOG_SQL" default:"false"` - LogToDisk bool `env:"LOG_TO_DISK" default:"false"` - LogUnixTS bool `env:"LOG_UNIX_TS" default:"false"` + JSONConsole bool `env:"JSON_CONSOLE" default:"false"` + LogFileDir string `env:"LOG_FILE_DIR"` + LogLevel zapcore.Level `env:"LOG_LEVEL"` + LogSQL bool `env:"LOG_SQL" default:"false"` + LogFileMaxSize utils.FileSize `env:"LOG_FILE_MAX_SIZE" default:"5120mb"` // 5120mb was determined based on previously collected logs, in which a daily log would be ~2.5GB and compressed would be ~210MB + LogFileMaxAge int64 `env:"LOG_FILE_MAX_AGE" default:"0"` + LogFileMaxBackups int64 `env:"LOG_FILE_MAX_BACKUPS" default:"1"` + LogUnixTS bool `env:"LOG_UNIX_TS" default:"false"` // Web Server AllowOrigins string `env:"ALLOW_ORIGINS" default:"http://localhost:3000,http://localhost:6688"` @@ -106,6 +125,7 @@ type ConfigSchema struct { // EVM/Ethereum // Legacy Eth ENV vars EthereumHTTPURL string `env:"ETH_HTTP_URL"` + EthereumNodes string `env:"EVM_NODES"` EthereumSecondaryURL string `env:"ETH_SECONDARY_URL"` //nodoc EthereumSecondaryURLs string `env:"ETH_SECONDARY_URLS"` EthereumURL string `env:"ETH_URL"` @@ -129,6 +149,11 @@ type ConfigSchema struct { MinIncomingConfirmations uint32 `env:"MIN_INCOMING_CONFIRMATIONS"` MinRequiredOutgoingConfirmations uint64 `env:"MIN_OUTGOING_CONFIRMATIONS"` MinimumContractPayment assets.Link `env:"MINIMUM_CONTRACT_PAYMENT_LINK_JUELS"` + // Node liveness checking + NodeNoNewHeadsThreshold time.Duration `env:"NODE_NO_NEW_HEADS_THRESHOLD"` + NodePollFailureThreshold uint32 `env:"NODE_POLL_FAILURE_THRESHOLD"` + NodePollInterval time.Duration `env:"NODE_POLL_INTERVAL"` + // EVM Gas Controls EvmEIP1559DynamicFees bool `env:"EVM_EIP1559_DYNAMIC_FEES"` EvmGasBumpPercent uint16 `env:"ETH_GAS_BUMP_PERCENT"` @@ -150,7 +175,7 @@ type ConfigSchema struct { BlockHistoryEstimatorBlockHistorySize uint16 `env:"BLOCK_HISTORY_ESTIMATOR_BLOCK_HISTORY_SIZE"` BlockHistoryEstimatorEIP1559FeeCapBufferBlocks uint16 `env:"BLOCK_HISTORY_ESTIMATOR_EIP1559_FEE_CAP_BUFFER_BLOCKS"` BlockHistoryEstimatorTransactionPercentile uint16 `env:"BLOCK_HISTORY_ESTIMATOR_TRANSACTION_PERCENTILE"` - // BPTXM + // Txm EvmGasBumpTxDepth uint16 `env:"ETH_GAS_BUMP_TX_DEPTH"` EvmMaxInFlightTransactions uint32 `env:"ETH_MAX_IN_FLIGHT_TRANSACTIONS"` EvmMaxQueuedTransactions uint64 `env:"ETH_MAX_QUEUED_TRANSACTIONS"` @@ -237,6 +262,7 @@ type ConfigSchema struct { KeeperDefaultTransactionQueueDepth uint32 `env:"KEEPER_DEFAULT_TRANSACTION_QUEUE_DEPTH" default:"1"` //nodoc KeeperGasPriceBufferPercent uint32 `env:"KEEPER_GAS_PRICE_BUFFER_PERCENT" default:"20"` KeeperGasTipCapBufferPercent uint32 `env:"KEEPER_GAS_TIP_CAP_BUFFER_PERCENT" default:"20"` + KeeperBaseFeeBufferPercent uint32 `env:"KEEPER_BASE_FEE_BUFFER_PERCENT" default:"20"` KeeperMaximumGracePeriod int64 `env:"KEEPER_MAXIMUM_GRACE_PERIOD" default:"100"` KeeperRegistryCheckGasOverhead uint64 `env:"KEEPER_REGISTRY_CHECK_GAS_OVERHEAD" default:"200000"` KeeperRegistryPerformGasOverhead uint64 `env:"KEEPER_REGISTRY_PERFORM_GAS_OVERHEAD" default:"150000"` diff --git a/core/config/envvar/schema_test.go b/core/config/envvar/schema_test.go index a2780b92e2d..a3a14c1ad70 100644 --- a/core/config/envvar/schema_test.go +++ b/core/config/envvar/schema_test.go @@ -4,8 +4,9 @@ import ( "reflect" "testing" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/core/utils" ) func TestConfigSchema(t *testing.T) { @@ -63,6 +64,7 @@ func TestConfigSchema(t *testing.T) { "EthereumSecondaryURL": "ETH_SECONDARY_URL", "EthereumSecondaryURLs": "ETH_SECONDARY_URLS", "EthereumURL": "ETH_URL", + "EthereumNodes": "EVM_NODES", "EvmBalanceMonitorBlockDelay": "ETH_BALANCE_MONITOR_BLOCK_DELAY", "EvmDefaultBatchSize": "ETH_DEFAULT_BATCH_SIZE", "EvmEIP1559DynamicFees": "EVM_EIP1559_DYNAMIC_FEES", @@ -116,6 +118,7 @@ func TestConfigSchema(t *testing.T) { "KeeperDefaultTransactionQueueDepth": "KEEPER_DEFAULT_TRANSACTION_QUEUE_DEPTH", "KeeperGasPriceBufferPercent": "KEEPER_GAS_PRICE_BUFFER_PERCENT", "KeeperGasTipCapBufferPercent": "KEEPER_GAS_TIP_CAP_BUFFER_PERCENT", + "KeeperBaseFeeBufferPercent": "KEEPER_BASE_FEE_BUFFER_PERCENT", "KeeperMaximumGracePeriod": "KEEPER_MAXIMUM_GRACE_PERIOD", "KeeperRegistryCheckGasOverhead": "KEEPER_REGISTRY_CHECK_GAS_OVERHEAD", "KeeperRegistryPerformGasOverhead": "KEEPER_REGISTRY_PERFORM_GAS_OVERHEAD", @@ -127,7 +130,9 @@ func TestConfigSchema(t *testing.T) { "LogFileDir": "LOG_FILE_DIR", "LogLevel": "LOG_LEVEL", "LogSQL": "LOG_SQL", - "LogToDisk": "LOG_TO_DISK", + "LogFileMaxSize": "LOG_FILE_MAX_SIZE", + "LogFileMaxAge": "LOG_FILE_MAX_AGE", + "LogFileMaxBackups": "LOG_FILE_MAX_BACKUPS", "LogUnixTS": "LOG_UNIX_TS", "MaximumServiceDuration": "MAXIMUM_SERVICE_DURATION", "MigrateDatabase": "MIGRATE_DATABASE", @@ -135,6 +140,9 @@ func TestConfigSchema(t *testing.T) { "MinRequiredOutgoingConfirmations": "MIN_OUTGOING_CONFIRMATIONS", "MinimumContractPayment": "MINIMUM_CONTRACT_PAYMENT_LINK_JUELS", "MinimumServiceDuration": "MINIMUM_SERVICE_DURATION", + "NodeNoNewHeadsThreshold": "NODE_NO_NEW_HEADS_THRESHOLD", + "NodePollFailureThreshold": "NODE_POLL_FAILURE_THRESHOLD", + "NodePollInterval": "NODE_POLL_INTERVAL", "ORMMaxIdleConns": "ORM_MAX_IDLE_CONNS", "ORMMaxOpenConns": "ORM_MAX_OPEN_CONNS", "OptimismGasFees": "OPTIMISM_GAS_FEES", @@ -156,8 +164,10 @@ func TestConfigSchema(t *testing.T) { "TLSRedirect": "CHAINLINK_TLS_REDIRECT", "TelemetryIngressBufferSize": "TELEMETRY_INGRESS_BUFFER_SIZE", "TelemetryIngressLogging": "TELEMETRY_INGRESS_LOGGING", + "TelemetryIngressUniConn": "TELEMETRY_INGRESS_UNICONN", "TelemetryIngressMaxBatchSize": "TELEMETRY_INGRESS_MAX_BATCH_SIZE", "TelemetryIngressSendInterval": "TELEMETRY_INGRESS_SEND_INTERVAL", + "TelemetryIngressSendTimeout": "TELEMETRY_INGRESS_SEND_TIMEOUT", "TelemetryIngressServerPubKey": "TELEMETRY_INGRESS_SERVER_PUB_KEY", "TelemetryIngressURL": "TELEMETRY_INGRESS_URL", "TelemetryIngressUseBatchSend": "TELEMETRY_INGRESS_USE_BATCH_SEND", diff --git a/core/config/general_config.go b/core/config/general_config.go index 6c37e33cd67..2faf946251f 100644 --- a/core/config/general_config.go +++ b/core/config/general_config.go @@ -107,6 +107,7 @@ type GeneralOnlyConfig interface { Dev() bool ShutdownGracePeriod() time.Duration EthereumHTTPURL() *url.URL + EthereumNodes() string EthereumSecondaryURLs() []url.URL EthereumURL() string ExplorerAccessKey() string @@ -127,6 +128,7 @@ type GeneralOnlyConfig interface { KeeperDefaultTransactionQueueDepth() uint32 KeeperGasPriceBufferPercent() uint32 KeeperGasTipCapBufferPercent() uint32 + KeeperBaseFeeBufferPercent() uint32 KeeperMaximumGracePeriod() int64 KeeperRegistryCheckGasOverhead() uint64 KeeperRegistryPerformGasOverhead() uint64 @@ -138,7 +140,9 @@ type GeneralOnlyConfig interface { LogFileDir() string LogLevel() zapcore.Level LogSQL() bool - LogToDisk() bool + LogFileMaxSize() utils.FileSize + LogFileMaxAge() int64 + LogFileMaxBackups() int64 LogUnixTimestamps() bool MigrateDatabase() bool ORMMaxIdleConns() int @@ -159,11 +163,13 @@ type GeneralOnlyConfig interface { TLSPort() uint16 TLSRedirect() bool TelemetryIngressLogging() bool + TelemetryIngressUniConn() bool TelemetryIngressServerPubKey() string TelemetryIngressURL() *url.URL TelemetryIngressBufferSize() uint TelemetryIngressMaxBatchSize() uint TelemetryIngressSendInterval() time.Duration + TelemetryIngressSendTimeout() time.Duration TelemetryIngressUseBatchSend() bool TriggerFallbackDBPollInterval() time.Duration UnAuthenticatedRateLimit() int64 @@ -215,6 +221,9 @@ type GlobalConfig interface { GlobalMinIncomingConfirmations() (uint32, bool) GlobalMinRequiredOutgoingConfirmations() (uint64, bool) GlobalMinimumContractPayment() (*assets.Link, bool) + GlobalNodeNoNewHeadsThreshold() (time.Duration, bool) + GlobalNodePollFailureThreshold() (uint32, bool) + GlobalNodePollInterval() (time.Duration, bool) OCR1Config OCR2Config @@ -311,27 +320,27 @@ func (c *generalConfig) Validate() error { } if _, exists := os.LookupEnv("ETH_DISABLED"); exists { - c.lggr.Warn(`DEPRECATION WARNING: ETH_DISABLED has been deprecated. + c.lggr.Error(`ETH_DISABLED is deprecated. -This warning will become a fatal error in a future release. Please switch to using one of the two options below instead: +This will become a fatal error in a future release. Please switch to using one of the two options below instead: - EVM_ENABLED=false - set this if you wish to completely disable all EVM chains and jobs and prevent them from ever loading (this is probably the one you want). - EVM_RPC_ENABLED=false - set this if you wish to load all EVM chains and jobs, but prevent any RPC calls to the eth node (the old behaviour). `) } if _, exists := os.LookupEnv("EVM_DISABLED"); exists { - c.lggr.Warn(`DEPRECATION WARNING: EVM_DISABLED has been deprecated and superceded by EVM_ENABLED. + c.lggr.Error(`EVM_DISABLED is deprecated and superceded by EVM_ENABLED. -This warning will become a fatal error in a future release. Please use the following instead to disable EVM chains: +This will become a fatal error in a future release. Please use the following instead to disable EVM chains: EVM_ENABLED=false `) } - if _, err := c.OCRKeyBundleID(); errors.Cause(err) == ErrInvalid { + if _, err := c.OCRKeyBundleID(); errors.Is(errors.Cause(err), ErrInvalid) { return err } - if _, err := c.OCRTransmitterAddress(); errors.Cause(err) == ErrInvalid { + if _, err := c.OCRTransmitterAddress(); errors.Is(errors.Cause(err), ErrInvalid) { return err } if peers, err := c.P2PBootstrapPeers(); err == nil { @@ -357,22 +366,24 @@ EVM_ENABLED=false if len(c.EthereumSecondaryURLs()) > 0 { c.lggr.Warn("ETH_SECONDARY_URL/ETH_SECONDARY_URLS have no effect when ETH_URL is not set") } + } else if c.EthereumNodes() != "" { + return errors.Errorf("It is not permitted to set both ETH_URL (got %s) and EVM_NODES (got %s). Please set either one or the other.", c.EthereumURL(), c.EthereumNodes()) } // Warn on legacy OCR env vars if c.OCRDHTLookupInterval() != 0 { - c.lggr.Warn("OCR_DHT_LOOKUP_INTERVAL is deprecated, use P2P_DHT_LOOKUP_INTERVAL instead") + c.lggr.Error("OCR_DHT_LOOKUP_INTERVAL is deprecated, use P2P_DHT_LOOKUP_INTERVAL instead") } if c.OCRBootstrapCheckInterval() != 0 { - c.lggr.Warn("OCR_BOOTSTRAP_CHECK_INTERVAL is deprecated, use P2P_BOOTSTRAP_CHECK_INTERVAL instead") + c.lggr.Error("OCR_BOOTSTRAP_CHECK_INTERVAL is deprecated, use P2P_BOOTSTRAP_CHECK_INTERVAL instead") } if c.OCRIncomingMessageBufferSize() != 0 { - c.lggr.Warn("OCR_INCOMING_MESSAGE_BUFFER_SIZE is deprecated, use P2P_INCOMING_MESSAGE_BUFFER_SIZE instead") + c.lggr.Error("OCR_INCOMING_MESSAGE_BUFFER_SIZE is deprecated, use P2P_INCOMING_MESSAGE_BUFFER_SIZE instead") } if c.OCROutgoingMessageBufferSize() != 0 { - c.lggr.Warn("OCR_OUTGOING_MESSAGE_BUFFER_SIZE is deprecated, use P2P_OUTGOING_MESSAGE_BUFFER_SIZE instead") + c.lggr.Error("OCR_OUTGOING_MESSAGE_BUFFER_SIZE is deprecated, use P2P_OUTGOING_MESSAGE_BUFFER_SIZE instead") } if c.OCRNewStreamTimeout() != 0 { - c.lggr.Warn("OCR_NEW_STREAM_TIMEOUT is deprecated, use P2P_NEW_STREAM_TIMEOUT instead") + c.lggr.Error("OCR_NEW_STREAM_TIMEOUT is deprecated, use P2P_NEW_STREAM_TIMEOUT instead") } switch c.DatabaseLockingMode() { @@ -385,8 +396,8 @@ EVM_ENABLED=false return errors.Errorf("LEASE_LOCK_REFRESH_INTERVAL must be less than or equal to half of LEASE_LOCK_DURATION (got LEASE_LOCK_REFRESH_INTERVAL=%s, LEASE_LOCK_DURATION=%s)", c.LeaseLockRefreshInterval().String(), c.LeaseLockDuration().String()) } - if c.viper.GetString(envvar.Name("LogFileDir")) != "" && !c.LogToDisk() { - c.lggr.Warn("LOG_FILE_DIR is ignored and has no effect when LOG_TO_DISK is not enabled") + if c.viper.GetString(envvar.Name("LogFileDir")) != "" && c.LogFileMaxSize() <= 0 { + c.lggr.Warn("LOG_FILE_DIR is ignored and has no effect when LOG_FILE_MAX_SIZE is not set to a value greater than zero") } return nil @@ -656,6 +667,12 @@ func (c *generalConfig) EthereumHTTPURL() (uri *url.URL) { return } +// EthereumNodes is a hack to allow node operators to give a JSON string that +// sets up multiple nodes +func (c *generalConfig) EthereumNodes() string { + return c.viper.GetString(envvar.Name("EthereumNodes")) +} + // EthereumSecondaryURLs is an optional backup RPC URL // Must be http(s) format // If specified, transactions will also be broadcast to this ethereum node @@ -793,6 +810,12 @@ func (c *generalConfig) KeeperGasTipCapBufferPercent() uint32 { return c.viper.GetUint32(envvar.Name("KeeperGasTipCapBufferPercent")) } +// KeeperBaseFeeBufferPercent adds the specified percentage to the base fee +// used for checking whether to perform an upkeep. Only applies in EIP-1559 mode. +func (c *generalConfig) KeeperBaseFeeBufferPercent() uint32 { + return c.viper.GetUint32(envvar.Name("KeeperBaseFeeBufferPercent")) +} + // KeeperRegistrySyncInterval is the interval in which the RegistrySynchronizer performs a full // sync of the keeper registry contract it is tracking func (c *generalConfig) KeeperRegistrySyncInterval() time.Duration { @@ -877,6 +900,11 @@ func (c *generalConfig) TelemetryIngressSendInterval() time.Duration { return c.getDuration("TelemetryIngressSendInterval") } +// TelemetryIngressSendTimeout is the max duration to wait for the request to complete when sending batch telemetry +func (c *generalConfig) TelemetryIngressSendTimeout() time.Duration { + return c.getDuration("TelemetryIngressSendTimeout") +} + // TelemetryIngressUseBatchSend toggles sending telemetry using the batch client to the ingress server func (c *generalConfig) TelemetryIngressUseBatchSend() bool { return c.viper.GetBool(envvar.Name("TelemetryIngressUseBatchSend")) @@ -887,6 +915,11 @@ func (c *generalConfig) TelemetryIngressLogging() bool { return c.getWithFallback("TelemetryIngressLogging", parse.Bool).(bool) } +// TelemetryIngressUniconn toggles which ws connection style is used. +func (c *generalConfig) TelemetryIngressUniConn() bool { + return c.getWithFallback("TelemetryIngressUniConn", parse.Bool).(bool) +} + func (c *generalConfig) ORMMaxOpenConns() int { return int(c.getWithFallback("ORMMaxOpenConns", parse.Uint16).(uint16)) } @@ -915,9 +948,20 @@ func (c *generalConfig) SetLogLevel(lvl zapcore.Level) error { return nil } -// LogToDisk configures disk preservation of logs. -func (c *generalConfig) LogToDisk() bool { - return c.getEnvWithFallback(envvar.LogToDisk).(bool) +// LogFileMaxSize configures disk preservation of logs max size (in megabytes) before file rotation. +func (c *generalConfig) LogFileMaxSize() utils.FileSize { + return c.getWithFallback("LogFileMaxSize", parse.FileSize).(utils.FileSize) +} + +// LogFileMaxAge configures disk preservation of logs max age (in days) before file rotation. +func (c *generalConfig) LogFileMaxAge() int64 { + return c.getWithFallback("LogFileMaxAge", parse.Int64).(int64) +} + +// LogFileMaxBackups configures disk preservation of the max amount of old log files to retain. +// If this is set to 0, the node will retain all old log files instead. +func (c *generalConfig) LogFileMaxBackups() int64 { + return c.getWithFallback("LogFileMaxBackups", parse.Int64).(int64) } // LogSQL tells chainlink to log all SQL statements made using the default logger @@ -1415,6 +1459,30 @@ func (c *generalConfig) GlobalEvmGasTipCapMinimum() (*big.Int, bool) { return val.(*big.Int), ok } +func (c *generalConfig) GlobalNodeNoNewHeadsThreshold() (time.Duration, bool) { + val, ok := c.lookupEnv(envvar.Name("NodeNoNewHeadsThreshold"), parse.Duration) + if val == nil { + return 0, false + } + return val.(time.Duration), ok +} + +func (c *generalConfig) GlobalNodePollFailureThreshold() (uint32, bool) { + val, ok := c.lookupEnv(envvar.Name("NodePollFailureThreshold"), parse.Uint32) + if val == nil { + return 0, false + } + return val.(uint32), ok +} + +func (c *generalConfig) GlobalNodePollInterval() (time.Duration, bool) { + val, ok := c.lookupEnv(envvar.Name("NodePollInterval"), parse.Duration) + if val == nil { + return 0, false + } + return val.(time.Duration), ok +} + // DatabaseLockingMode can be one of 'dual', 'advisorylock', 'lease' or 'none' // It controls which mode to use to enforce that only one Chainlink application can use the database func (c *generalConfig) DatabaseLockingMode() string { diff --git a/core/config/mocks/general_config.go b/core/config/mocks/general_config.go index 8496a512930..19243a0f2e2 100644 --- a/core/config/mocks/general_config.go +++ b/core/config/mocks/general_config.go @@ -641,6 +641,20 @@ func (_m *GeneralConfig) EthereumHTTPURL() *url.URL { return r0 } +// EthereumNodes provides a mock function with given fields: +func (_m *GeneralConfig) EthereumNodes() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // EthereumSecondaryURLs provides a mock function with given fields: func (_m *GeneralConfig) EthereumSecondaryURLs() []url.URL { ret := _m.Called() @@ -1718,6 +1732,69 @@ func (_m *GeneralConfig) GlobalMinimumContractPayment() (*assets.Link, bool) { return r0, r1 } +// GlobalNodeNoNewHeadsThreshold provides a mock function with given fields: +func (_m *GeneralConfig) GlobalNodeNoNewHeadsThreshold() (time.Duration, bool) { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GlobalNodePollFailureThreshold provides a mock function with given fields: +func (_m *GeneralConfig) GlobalNodePollFailureThreshold() (uint32, bool) { + ret := _m.Called() + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GlobalNodePollInterval provides a mock function with given fields: +func (_m *GeneralConfig) GlobalNodePollInterval() (time.Duration, bool) { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // GlobalOCRContractConfirmations provides a mock function with given fields: func (_m *GeneralConfig) GlobalOCRContractConfirmations() (uint16, bool) { ret := _m.Called() @@ -1914,6 +1991,20 @@ func (_m *GeneralConfig) JobPipelineResultWriteQueueDepth() uint64 { return r0 } +// KeeperBaseFeeBufferPercent provides a mock function with given fields: +func (_m *GeneralConfig) KeeperBaseFeeBufferPercent() uint32 { + ret := _m.Called() + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + // KeeperCheckUpkeepGasPriceFeatureEnabled provides a mock function with given fields: func (_m *GeneralConfig) KeeperCheckUpkeepGasPriceFeatureEnabled() bool { ret := _m.Called() @@ -2096,36 +2187,64 @@ func (_m *GeneralConfig) LogFileDir() string { return r0 } -// LogLevel provides a mock function with given fields: -func (_m *GeneralConfig) LogLevel() zapcore.Level { +// LogFileMaxAge provides a mock function with given fields: +func (_m *GeneralConfig) LogFileMaxAge() int64 { ret := _m.Called() - var r0 zapcore.Level - if rf, ok := ret.Get(0).(func() zapcore.Level); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() } else { - r0 = ret.Get(0).(zapcore.Level) + r0 = ret.Get(0).(int64) } return r0 } -// LogSQL provides a mock function with given fields: -func (_m *GeneralConfig) LogSQL() bool { +// LogFileMaxBackups provides a mock function with given fields: +func (_m *GeneralConfig) LogFileMaxBackups() int64 { ret := _m.Called() - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() } else { - r0 = ret.Get(0).(bool) + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// LogFileMaxSize provides a mock function with given fields: +func (_m *GeneralConfig) LogFileMaxSize() utils.FileSize { + ret := _m.Called() + + var r0 utils.FileSize + if rf, ok := ret.Get(0).(func() utils.FileSize); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(utils.FileSize) + } + + return r0 +} + +// LogLevel provides a mock function with given fields: +func (_m *GeneralConfig) LogLevel() zapcore.Level { + ret := _m.Called() + + var r0 zapcore.Level + if rf, ok := ret.Get(0).(func() zapcore.Level); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(zapcore.Level) } return r0 } -// LogToDisk provides a mock function with given fields: -func (_m *GeneralConfig) LogToDisk() bool { +// LogSQL provides a mock function with given fields: +func (_m *GeneralConfig) LogSQL() bool { ret := _m.Called() var r0 bool @@ -3230,6 +3349,20 @@ func (_m *GeneralConfig) TelemetryIngressSendInterval() time.Duration { return r0 } +// TelemetryIngressSendTimeout provides a mock function with given fields: +func (_m *GeneralConfig) TelemetryIngressSendTimeout() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + // TelemetryIngressServerPubKey provides a mock function with given fields: func (_m *GeneralConfig) TelemetryIngressServerPubKey() string { ret := _m.Called() @@ -3260,6 +3393,20 @@ func (_m *GeneralConfig) TelemetryIngressURL() *url.URL { return r0 } +// TelemetryIngressUniConn provides a mock function with given fields: +func (_m *GeneralConfig) TelemetryIngressUniConn() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // TelemetryIngressUseBatchSend provides a mock function with given fields: func (_m *GeneralConfig) TelemetryIngressUseBatchSend() bool { ret := _m.Called() diff --git a/core/config/p2p_config.go b/core/config/p2p_config.go index de4dfd16ac1..c5d509e2d0b 100644 --- a/core/config/p2p_config.go +++ b/core/config/p2p_config.go @@ -46,7 +46,7 @@ func (c *generalConfig) P2PPeerID() p2pkey.PeerID { } var pid p2pkey.PeerID if err := pid.UnmarshalText([]byte(pidStr)); err != nil { - c.lggr.Error(errors.Wrapf(ErrInvalid, "P2P_PEER_ID is invalid %v", err)) + c.lggr.Critical(errors.Wrapf(ErrInvalid, "P2P_PEER_ID is invalid %v", err)) return "" } return pid diff --git a/core/config/p2p_v1_config.go b/core/config/p2p_v1_config.go index a2ae083252c..74045269283 100644 --- a/core/config/p2p_v1_config.go +++ b/core/config/p2p_v1_config.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pkg/errors" + ocrnetworking "github.com/smartcontractkit/libocr/networking" "github.com/smartcontractkit/chainlink/core/config/envvar" "github.com/smartcontractkit/chainlink/core/config/parse" @@ -53,6 +54,15 @@ func (c *generalConfig) P2PListenPort() uint16 { if c.viper.IsSet(envvar.Name("P2PListenPort")) { return uint16(c.viper.GetUint32(envvar.Name("P2PListenPort"))) } + switch c.P2PNetworkingStack() { + case ocrnetworking.NetworkingStackV1, ocrnetworking.NetworkingStackV1V2: + return c.randomP2PListenPort() + default: + return 0 + } +} + +func (c *generalConfig) randomP2PListenPort() uint16 { // Fast path in case it was already set c.randomP2PPortMtx.RLock() if c.randomP2PPort > 0 { diff --git a/core/config/presenters.go b/core/config/presenters.go index 6bb4ea13720..84a4ca6030c 100644 --- a/core/config/presenters.go +++ b/core/config/presenters.go @@ -63,6 +63,7 @@ type EnvPrinter struct { KeeperDefaultTransactionQueueDepth uint32 `json:"KEEPER_DEFAULT_TRANSACTION_QUEUE_DEPTH"` KeeperGasPriceBufferPercent uint32 `json:"KEEPER_GAS_PRICE_BUFFER_PERCENT"` KeeperGasTipCapBufferPercent uint32 `json:"KEEPER_GAS_TIP_CAP_BUFFER_PERCENT"` + KeeperBaseFeeBufferPercent uint32 `json:"KEEPER_BASE_FEE_BUFFER_PERCENT"` KeeperMaximumGracePeriod int64 `json:"KEEPER_MAXIMUM_GRACE_PERIOD"` KeeperRegistryCheckGasOverhead uint64 `json:"KEEPER_REGISTRY_CHECK_GAS_OVERHEAD"` KeeperRegistryPerformGasOverhead uint64 `json:"KEEPER_REGISTRY_PERFORM_GAS_OVERHEAD"` @@ -76,7 +77,9 @@ type EnvPrinter struct { LogFileDir string `json:"LOG_FILE_DIR"` LogLevel zapcore.Level `json:"LOG_LEVEL"` LogSQL bool `json:"LOG_SQL"` - LogToDisk bool `json:"LOG_TO_DISK"` + LogFileMaxSize utils.FileSize `json:"LOG_FILE_MAX_SIZE"` + LogFileMaxAge int64 `json:"LOG_FILE_MAX_AGE"` + LogFileMaxBackups int64 `json:"LOG_FILE_MAX_BACKUPS"` TriggerFallbackDBPollInterval time.Duration `json:"JOB_PIPELINE_DB_POLL_INTERVAL"` // OCR1 @@ -138,42 +141,46 @@ func NewConfigPrinter(cfg GeneralConfig) ConfigPrinter { ocrDatabaseTimeout, _ := cfg.GlobalOCRDatabaseTimeout() return ConfigPrinter{ EnvPrinter: EnvPrinter{ - AdvisoryLockCheckInterval: cfg.AdvisoryLockCheckInterval(), - AdvisoryLockID: cfg.AdvisoryLockID(), - AllowOrigins: cfg.AllowOrigins(), - BlockBackfillDepth: cfg.BlockBackfillDepth(), - BridgeResponseURL: cfg.BridgeResponseURL().String(), - ClientNodeURL: cfg.ClientNodeURL(), - DatabaseBackupFrequency: cfg.DatabaseBackupFrequency(), - DatabaseBackupMode: string(cfg.DatabaseBackupMode()), - DatabaseBackupOnVersionUpgrade: cfg.DatabaseBackupOnVersionUpgrade(), - DatabaseLockingMode: cfg.DatabaseLockingMode(), - DefaultChainID: cfg.DefaultChainID().String(), - DefaultHTTPLimit: cfg.DefaultHTTPLimit(), - DefaultHTTPTimeout: cfg.DefaultHTTPTimeout(), - Dev: cfg.Dev(), - ShutdownGracePeriod: cfg.ShutdownGracePeriod(), - EVMRPCEnabled: cfg.EVMRPCEnabled(), - EthereumHTTPURL: ethereumHTTPURL, - EthereumSecondaryURLs: mapToStringA(cfg.EthereumSecondaryURLs()), - EthereumURL: cfg.EthereumURL(), - ExplorerURL: explorerURL, - FMDefaultTransactionQueueDepth: cfg.FMDefaultTransactionQueueDepth(), - FeatureExternalInitiators: cfg.FeatureExternalInitiators(), - FeatureOffchainReporting: cfg.FeatureOffchainReporting(), - InsecureFastScrypt: cfg.InsecureFastScrypt(), - JSONConsole: cfg.JSONConsole(), - JobPipelineReaperInterval: cfg.JobPipelineReaperInterval(), - JobPipelineReaperThreshold: cfg.JobPipelineReaperThreshold(), - KeeperDefaultTransactionQueueDepth: cfg.KeeperDefaultTransactionQueueDepth(), - KeeperGasPriceBufferPercent: cfg.KeeperGasPriceBufferPercent(), - KeeperGasTipCapBufferPercent: cfg.KeeperGasTipCapBufferPercent(), - LeaseLockDuration: cfg.LeaseLockDuration(), - LeaseLockRefreshInterval: cfg.LeaseLockRefreshInterval(), - LogFileDir: cfg.LogFileDir(), - LogLevel: cfg.LogLevel(), - LogSQL: cfg.LogSQL(), - LogToDisk: cfg.LogToDisk(), + AdvisoryLockCheckInterval: cfg.AdvisoryLockCheckInterval(), + AdvisoryLockID: cfg.AdvisoryLockID(), + AllowOrigins: cfg.AllowOrigins(), + BlockBackfillDepth: cfg.BlockBackfillDepth(), + BridgeResponseURL: cfg.BridgeResponseURL().String(), + ClientNodeURL: cfg.ClientNodeURL(), + DatabaseBackupFrequency: cfg.DatabaseBackupFrequency(), + DatabaseBackupMode: string(cfg.DatabaseBackupMode()), + DatabaseBackupOnVersionUpgrade: cfg.DatabaseBackupOnVersionUpgrade(), + DatabaseLockingMode: cfg.DatabaseLockingMode(), + DefaultChainID: cfg.DefaultChainID().String(), + DefaultHTTPLimit: cfg.DefaultHTTPLimit(), + DefaultHTTPTimeout: cfg.DefaultHTTPTimeout(), + Dev: cfg.Dev(), + ShutdownGracePeriod: cfg.ShutdownGracePeriod(), + EVMRPCEnabled: cfg.EVMRPCEnabled(), + EthereumHTTPURL: ethereumHTTPURL, + EthereumSecondaryURLs: mapToStringA(cfg.EthereumSecondaryURLs()), + EthereumURL: cfg.EthereumURL(), + ExplorerURL: explorerURL, + FMDefaultTransactionQueueDepth: cfg.FMDefaultTransactionQueueDepth(), + FeatureExternalInitiators: cfg.FeatureExternalInitiators(), + FeatureOffchainReporting: cfg.FeatureOffchainReporting(), + InsecureFastScrypt: cfg.InsecureFastScrypt(), + JSONConsole: cfg.JSONConsole(), + JobPipelineReaperInterval: cfg.JobPipelineReaperInterval(), + JobPipelineReaperThreshold: cfg.JobPipelineReaperThreshold(), + KeeperCheckUpkeepGasPriceFeatureEnabled: cfg.KeeperCheckUpkeepGasPriceFeatureEnabled(), + KeeperDefaultTransactionQueueDepth: cfg.KeeperDefaultTransactionQueueDepth(), + KeeperGasPriceBufferPercent: cfg.KeeperGasPriceBufferPercent(), + KeeperGasTipCapBufferPercent: cfg.KeeperGasTipCapBufferPercent(), + KeeperBaseFeeBufferPercent: cfg.KeeperBaseFeeBufferPercent(), + LeaseLockDuration: cfg.LeaseLockDuration(), + LeaseLockRefreshInterval: cfg.LeaseLockRefreshInterval(), + LogFileDir: cfg.LogFileDir(), + LogFileMaxSize: cfg.LogFileMaxSize(), + LogFileMaxAge: cfg.LogFileMaxAge(), + LogFileMaxBackups: cfg.LogFileMaxBackups(), + LogLevel: cfg.LogLevel(), + LogSQL: cfg.LogSQL(), // OCRV1 OCRContractTransmitterTransmitTimeout: ocrTransmitTimeout, diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 3642c74d6ac..484e49a718d 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -12,8 +12,8 @@ import ( "net/http" "net/http/httptest" "net/url" + "path/filepath" "reflect" - "strings" "testing" "time" @@ -24,7 +24,6 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" - "github.com/gorilla/websocket" cryptop2p "github.com/libp2p/go-libp2p-core/crypto" p2ppeer "github.com/libp2p/go-libp2p-core/peer" "github.com/manyminds/api2go/jsonapi" @@ -34,18 +33,21 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v4" + "gopkg.in/guregu/null.v4" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" "github.com/smartcontractkit/chainlink/core/chains/evm/gas" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" evmMocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/chains/terra" "github.com/smartcontractkit/chainlink/core/cmd" @@ -77,8 +79,6 @@ import ( "github.com/smartcontractkit/chainlink/core/web" webauth "github.com/smartcontractkit/chainlink/core/web/auth" webpresenters "github.com/smartcontractkit/chainlink/core/web/presenters" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/smartcontractkit/sqlx" // Force import of pgtest to ensure that txdb is registered as a DB driver _ "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -181,15 +181,15 @@ func NewJobPipelineV2(t testing.TB, cfg config.GeneralConfig, cc evm.ChainSet, d } } -// NewEthBroadcaster creates a new bulletprooftxmanager.EthBroadcaster for use in testing. -func NewEthBroadcaster(t testing.TB, db *sqlx.DB, ethClient evmclient.Client, keyStore bulletprooftxmanager.KeyStore, config evmconfig.ChainScopedConfig, keyStates []ethkey.State, checkerFactory bulletprooftxmanager.TransmitCheckerFactory) *bulletprooftxmanager.EthBroadcaster { +// NewEthBroadcaster creates a new txmgr.EthBroadcaster for use in testing. +func NewEthBroadcaster(t testing.TB, db *sqlx.DB, ethClient evmclient.Client, keyStore txmgr.KeyStore, config evmconfig.ChainScopedConfig, keyStates []ethkey.State, checkerFactory txmgr.TransmitCheckerFactory) *txmgr.EthBroadcaster { t.Helper() eventBroadcaster := NewEventBroadcaster(t, config.DatabaseURL()) - err := eventBroadcaster.Start() + err := eventBroadcaster.Start(testutils.Context(t.(*testing.T))) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, eventBroadcaster.Close()) }) lggr := logger.TestLogger(t) - return bulletprooftxmanager.NewEthBroadcaster(db, ethClient, config, keyStore, eventBroadcaster, + return txmgr.NewEthBroadcaster(db, ethClient, config, keyStore, eventBroadcaster, keyStates, gas.NewFixedPriceEstimator(config, lggr), nil, lggr, checkerFactory) } @@ -199,10 +199,10 @@ func NewEventBroadcaster(t testing.TB, dbURL url.URL) pg.EventBroadcaster { return pg.NewEventBroadcaster(dbURL, 0, 0, lggr, uuid.NewV4()) } -func NewEthConfirmer(t testing.TB, db *sqlx.DB, ethClient evmclient.Client, config evmconfig.ChainScopedConfig, ks keystore.Eth, keyStates []ethkey.State, fn bulletprooftxmanager.ResumeCallback) *bulletprooftxmanager.EthConfirmer { +func NewEthConfirmer(t testing.TB, db *sqlx.DB, ethClient evmclient.Client, config evmconfig.ChainScopedConfig, ks keystore.Eth, keyStates []ethkey.State, fn txmgr.ResumeCallback) *txmgr.EthConfirmer { t.Helper() lggr := logger.TestLogger(t) - ec := bulletprooftxmanager.NewEthConfirmer(db, ethClient, config, ks, keyStates, + ec := txmgr.NewEthConfirmer(db, ethClient, config, ks, keyStates, gas.NewFixedPriceEstimator(config, lggr), fn, lggr) return ec } @@ -218,82 +218,12 @@ type TestApplication struct { Key ethkey.KeyV2 } -// jsonrpcHandler is called with the method and request param(s). -// respResult will be sent immediately. notifyResult is optional, and sent after a short delay. -type jsonrpcHandler func(reqMethod string, reqParams gjson.Result) (respResult, notifyResult string) - // NewWSServer starts a websocket server which invokes callback for each message received. // If chainID is set, then eth_chainId calls will be automatically handled. -func NewWSServer(t *testing.T, chainID *big.Int, callback jsonrpcHandler) string { - upgrader := websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { return true }, - } - lggr := logger.TestLogger(t).Named("WSServer") - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - require.NoError(t, err, "Failed to upgrade WS connection") - defer conn.Close() - for { - _, data, err := conn.ReadMessage() - if err != nil { - if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseAbnormalClosure) { - lggr.Info("Closing") - return - } - lggr.Errorw("Failed to read message", "err", err) - return - } - lggr.Debugw("Received message", "wsmsg", string(data)) - req := ParseJSON(t, bytes.NewReader(data)) - if !req.IsObject() { - lggr.Errorw("Request must be object", "type", req.Type) - return - } - if e := req.Get("error"); e.Exists() { - lggr.Warnw("Received jsonrpc error message", "err", e) - break - } - m := req.Get("method") - if m.Type != gjson.String { - lggr.Errorw("method must be string", "type", m.Type) - return - } - - var resp, notify string - if chainID != nil && m.String() == "eth_chainId" { - resp = `"0x` + chainID.Text(16) + `"` - } else { - resp, notify = callback(m.String(), req.Get("params")) - } - id := req.Get("id") - msg := fmt.Sprintf(`{"jsonrpc":"2.0","id":%s,"result":%s}`, id, resp) - lggr.Debugw("Sending message", "wsmsg", msg) - err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) - if err != nil { - lggr.Errorw("Failed to write message", "err", err) - return - } - - if notify != "" { - time.Sleep(100 * time.Millisecond) - msg := fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":%s}}`, notify) - lggr.Debugw("Sending message", "wsmsg", msg) - err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) - if err != nil { - lggr.Errorw("Failed to write message", "err", err) - return - } - } - } - }) - server := httptest.NewServer(handler) +func NewWSServer(t *testing.T, chainID *big.Int, callback testutils.JSONRPCHandler) string { + server := testutils.NewWSServer(t, chainID, callback) t.Cleanup(server.Close) - - u, err := url.Parse(server.URL) - require.NoError(t, err, "Failed to parse url") - u.Scheme = "ws" - - return u.String() + return server.WSURL().String() } func NewTestGeneralConfig(t testing.TB) *configtest.TestGeneralConfig { @@ -302,6 +232,7 @@ func NewTestGeneralConfig(t testing.TB) *configtest.TestGeneralConfig { SecretGenerator: MockSecretGenerator{}, Dialect: dialects.TransactionWrappedPostgres, AdvisoryLockID: null.IntFrom(NewRandomInt64()), + P2PEnabled: null.BoolFrom(false), ShutdownGracePeriod: &shutdownGracePeriod, } return configtest.NewTestGeneralConfigWithOverrides(t, overrides) @@ -370,6 +301,7 @@ const ( // This should only be used in full integration tests. For controller tests, see NewApplicationEVMDisabled func NewApplicationWithConfig(t testing.TB, cfg *configtest.TestGeneralConfig, flagsAndDeps ...interface{}) *TestApplication { t.Helper() + testutils.SkipShortDB(t) lggr := logger.TestLogger(t) @@ -414,7 +346,7 @@ func NewApplicationWithConfig(t testing.TB, cfg *configtest.TestGeneralConfig, f ethClient = evmclient.NewNullClient(nil, lggr) } if chainORM == nil { - chainORM = evm.NewORM(db) + chainORM = evm.NewORM(db, lggr, cfg) } keyStore := keystore.New(db, utils.FastScryptParams, lggr, cfg) @@ -436,17 +368,19 @@ func NewApplicationWithConfig(t testing.TB, cfg *configtest.TestGeneralConfig, f if err != nil { lggr.Fatal(err) } - terraLggr := lggr.Named("Terra") - chains.Terra, err = terra.NewChainSet(terra.ChainSetOpts{ - Config: cfg, - Logger: terraLggr, - DB: db, - KeyStore: keyStore.Terra(), - EventBroadcaster: eventBroadcaster, - ORM: terra.NewORM(db, terraLggr, cfg), - }) - if err != nil { - lggr.Fatal(err) + if cfg.TerraEnabled() { + terraLggr := lggr.Named("Terra") + chains.Terra, err = terra.NewChainSet(terra.ChainSetOpts{ + Config: cfg, + Logger: terraLggr, + DB: db, + KeyStore: keyStore.Terra(), + EventBroadcaster: eventBroadcaster, + ORM: terra.NewORM(db, terraLggr, cfg), + }) + if err != nil { + lggr.Fatal(err) + } } appInstance, err := chainlink.NewApplication(chainlink.ApplicationOpts{ @@ -456,6 +390,7 @@ func NewApplicationWithConfig(t testing.TB, cfg *configtest.TestGeneralConfig, f KeyStore: keyStore, Chains: chains, Logger: lggr, + CloseLogger: lggr.Sync, ExternalInitiatorManager: externalInitiatorManager, }) require.NoError(t, err) @@ -476,25 +411,23 @@ func NewApplicationWithConfig(t testing.TB, cfg *configtest.TestGeneralConfig, f return ta } -func NewEthMocksWithDefaultChain(t testing.TB) (c *evmMocks.Client, s *evmMocks.Subscription, f func()) { - c, s, f = NewEthMocks(t) +func NewEthMocksWithDefaultChain(t testing.TB) (c *evmMocks.Client, s *evmMocks.Subscription) { + testutils.SkipShortDB(t) + c, s = NewEthMocks(t) c.On("ChainID").Return(&FixtureChainID).Maybe() return } -func NewEthMocks(t testing.TB) (*evmMocks.Client, *evmMocks.Subscription, func()) { +func NewEthMocks(t testing.TB) (*evmMocks.Client, *evmMocks.Subscription) { c, s := NewEthClientAndSubMock(t) - var assertMocksCalled func() switch tt := t.(type) { case *testing.T: - assertMocksCalled = func() { + t.Cleanup(func() { c.AssertExpectations(tt) s.AssertExpectations(tt) - } - case *testing.B: - assertMocksCalled = func() {} + }) } - return c, s, assertMocksCalled + return c, s } func NewEthClientAndSubMock(t mock.TestingT) (*evmMocks.Client, *evmMocks.Subscription) { @@ -523,8 +456,9 @@ func NewEthClientMockWithDefaultChain(t testing.TB) *evmMocks.Client { return c } -func NewEthMocksWithStartupAssertions(t testing.TB) (*evmMocks.Client, *evmMocks.Subscription, func()) { - c, s, assertMocksCalled := NewEthMocks(t) +func NewEthMocksWithStartupAssertions(t testing.TB) *evmMocks.Client { + testutils.SkipShort(t, "long test") + c, s := NewEthMocks(t) c.On("Dial", mock.Anything).Maybe().Return(nil) c.On("SubscribeNewHead", mock.Anything, mock.Anything).Maybe().Return(EmptyMockSubscription(t), nil) c.On("SendTransaction", mock.Anything, mock.Anything).Maybe().Return(nil) @@ -539,12 +473,13 @@ func NewEthMocksWithStartupAssertions(t testing.TB) (*evmMocks.Client, *evmMocks s.On("Err").Return(nil).Maybe() s.On("Unsubscribe").Return(nil).Maybe() - return c, s, assertMocksCalled + return c } // NewEthMocksWithTransactionsOnBlocksAssertions sets an Eth mock with transactions on blocks -func NewEthMocksWithTransactionsOnBlocksAssertions(t testing.TB) (*evmMocks.Client, *evmMocks.Subscription, func()) { - c, s, assertMocksCalled := NewEthMocks(t) +func NewEthMocksWithTransactionsOnBlocksAssertions(t testing.TB) *evmMocks.Client { + testutils.SkipShort(t, "long test") + c, s := NewEthMocks(t) c.On("Dial", mock.Anything).Maybe().Return(nil) c.On("SubscribeNewHead", mock.Anything, mock.Anything).Maybe().Return(EmptyMockSubscription(t), nil) c.On("SendTransaction", mock.Anything, mock.Anything).Maybe().Return(nil) @@ -574,7 +509,8 @@ func NewEthMocksWithTransactionsOnBlocksAssertions(t testing.TB) (*evmMocks.Clie s.On("Err").Return(nil).Maybe() s.On("Unsubscribe").Return(nil).Maybe() - return c, s, assertMocksCalled + + return c } func newServer(app chainlink.Application) *httptest.Server { @@ -583,7 +519,7 @@ func newServer(app chainlink.Application) *httptest.Server { } // Start starts the chainlink app and registers Stop to clean up at end of test. -func (ta *TestApplication) Start() error { +func (ta *TestApplication) Start(ctx context.Context) error { ta.t.Helper() ta.Started = true err := ta.ChainlinkApplication.KeyStore.Unlock(Password) @@ -591,7 +527,7 @@ func (ta *TestApplication) Start() error { return err } - err = ta.ChainlinkApplication.Start() + err = ta.ChainlinkApplication.Start(ctx) if err != nil { return err } @@ -647,10 +583,12 @@ func (ta *TestApplication) NewHTTPClient() HTTPClientCleaner { func (ta *TestApplication) NewClientAndRenderer() (*cmd.Client, *RendererMock) { sessionID := ta.MustSeedNewSession() r := &RendererMock{} + lggr := logger.TestLogger(ta.t) client := &cmd.Client{ Renderer: r, Config: ta.GetConfig(), - Logger: logger.TestLogger(ta.t), + Logger: lggr, + CloseLogger: lggr.Sync, AppFactory: seededAppFactory{ta.ChainlinkApplication}, FallbackAPIInitializer: NewMockAPIInitializer(ta.t), Runner: EmptyRunner{}, @@ -670,6 +608,7 @@ func (ta *TestApplication) NewAuthenticatingClient(prompter cmd.Prompter) *cmd.C Renderer: &RendererMock{}, Config: ta.GetConfig(), Logger: lggr, + CloseLogger: lggr.Sync, AppFactory: seededAppFactory{ta.ChainlinkApplication}, FallbackAPIInitializer: NewMockAPIInitializer(ta.t), Runner: EmptyRunner{}, @@ -805,33 +744,11 @@ func ParseJSONAPIResponseMetaCount(input []byte) (int, error) { // ReadLogs returns the contents of the applications log file as a string func ReadLogs(dir string) (string, error) { - logFile := fmt.Sprintf("%s/log.jsonl", dir) + logFile := filepath.Join(dir, logger.LogsFile) b, err := ioutil.ReadFile(logFile) return string(b), err } -// FindLogMessage returns the message from the first JSON log line for which test returns true. -func FindLogMessage(logs string, test func(msg string) bool) string { - for _, l := range strings.Split(logs, "\n") { - var line map[string]interface{} - if json.Unmarshal([]byte(l), &line) != nil { - continue - } - m, ok := line["msg"] - if !ok { - continue - } - ms, ok := m.(string) - if !ok { - continue - } - if test(ms) { - return ms - } - } - return "" -} - func CreateJobViaWeb(t testing.TB, app *TestApplication, request []byte) job.Job { t.Helper() @@ -1013,13 +930,13 @@ func AssertPipelineRunsStays(t testing.TB, pipelineSpecID int32, db *sqlx.DB, wa } // AssertEthTxAttemptCountStays asserts that the number of tx attempts remains at the provided value -func AssertEthTxAttemptCountStays(t testing.TB, db *sqlx.DB, want int) []bulletprooftxmanager.EthTxAttempt { +func AssertEthTxAttemptCountStays(t testing.TB, db *sqlx.DB, want int) []txmgr.EthTxAttempt { g := gomega.NewWithT(t) - var txas []bulletprooftxmanager.EthTxAttempt + var txas []txmgr.EthTxAttempt var err error - g.Consistently(func() []bulletprooftxmanager.EthTxAttempt { - txas = make([]bulletprooftxmanager.EthTxAttempt, 0) + g.Consistently(func() []txmgr.EthTxAttempt { + txas = make([]txmgr.EthTxAttempt, 0) err = db.Select(&txas, `SELECT * FROM eth_tx_attempts ORDER BY id ASC`) assert.NoError(t, err) return txas @@ -1253,11 +1170,7 @@ func CallbackOrTimeout(t testing.TB, msg string, callback func(), durationParams } func MustParseURL(t *testing.T, input string) *url.URL { - u, err := url.Parse(input) - if err != nil { - logger.TestLogger(t).Panic(err) - } - return u + return testutils.MustParseURL(t, input) } // EthereumLogIterator is the interface provided by gethwrapper representations of EVM @@ -1332,14 +1245,14 @@ func MockApplicationEthCalls(t *testing.T, app *TestApplication, ethClient *evmM } } -func BatchElemMatchesHash(req rpc.BatchElem, hash common.Hash) bool { - return req.Method == "eth_getTransactionReceipt" && - len(req.Args) == 1 && req.Args[0] == hash +func BatchElemMatchesParams(req rpc.BatchElem, arg interface{}, method string) bool { + return req.Method == method && + len(req.Args) == 1 && req.Args[0] == arg } -func BatchElemMustMatchHash(t *testing.T, req rpc.BatchElem, hash common.Hash) { +func BatchElemMustMatchParams(t *testing.T, req rpc.BatchElem, hash common.Hash, method string) { t.Helper() - if !BatchElemMatchesHash(req, hash) { + if !BatchElemMatchesParams(req, hash, method) { t.Fatalf("Batch hash %v does not match expected %v", req.Args[0], hash) } } @@ -1637,8 +1550,8 @@ func MustGetStateForKey(t testing.TB, kst keystore.Eth, key ethkey.KeyV2) ethkey return states[0] } -func NewBulletproofTxManagerORM(t *testing.T, db *sqlx.DB, cfg pg.LogConfig) bulletprooftxmanager.ORM { - return bulletprooftxmanager.NewORM(db, logger.TestLogger(t), cfg) +func NewTxmORM(t *testing.T, db *sqlx.DB, cfg pg.LogConfig) txmgr.ORM { + return txmgr.NewORM(db, logger.TestLogger(t), cfg) } // ClearDBTables deletes all rows from the given tables diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 375d05b0c12..eb9c6e2ee38 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -22,11 +22,13 @@ import ( "github.com/urfave/cli" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -39,7 +41,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) func NewEIP55Address() ethkey.EIP55Address { @@ -72,9 +73,9 @@ func NewBridgeType(t testing.TB, opts BridgeOpts) (*bridges.BridgeTypeAuthentica rnd := uuid.NewV4().String() if opts.Name != "" { - btr.Name = bridges.MustNewTaskType(opts.Name) + btr.Name = bridges.MustParseBridgeName(opts.Name) } else { - btr.Name = bridges.MustNewTaskType(fmt.Sprintf("test_bridge_%s", rnd)) + btr.Name = bridges.MustParseBridgeName(fmt.Sprintf("test_bridge_%s", rnd)) } if opts.URL != "" { @@ -130,18 +131,18 @@ func EmptyCLIContext() *cli.Context { return cli.NewContext(nil, set, nil) } -func NewEthTx(t *testing.T, fromAddress common.Address) bulletprooftxmanager.EthTx { - return bulletprooftxmanager.EthTx{ +func NewEthTx(t *testing.T, fromAddress common.Address) txmgr.EthTx { + return txmgr.EthTx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), EncodedPayload: []byte{1, 2, 3}, Value: assets.NewEthValue(142), GasLimit: uint64(1000000000), - State: bulletprooftxmanager.EthTxUnstarted, + State: txmgr.EthTxUnstarted, } } -func MustInsertUnconfirmedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) bulletprooftxmanager.EthTx { +func MustInsertUnconfirmedEthTx(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) txmgr.EthTx { broadcastAt := time.Now() chainID := &FixtureChainID for _, opt := range opts { @@ -157,13 +158,13 @@ func MustInsertUnconfirmedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, non etx.BroadcastAt = &broadcastAt n := nonce etx.Nonce = &n - etx.State = bulletprooftxmanager.EthTxUnconfirmed + etx.State = txmgr.EthTxUnconfirmed etx.EVMChainID = *utils.NewBig(chainID) require.NoError(t, borm.InsertEthTx(&etx)) return etx } -func MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) bulletprooftxmanager.EthTx { +func MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) txmgr.EthTx { etx := MustInsertUnconfirmedEthTx(t, borm, nonce, fromAddress, opts...) attempt := NewLegacyEthTxAttempt(t, etx.ID) @@ -172,14 +173,14 @@ func MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t *testing.T, borm bul require.NoError(t, tx.EncodeRLP(rlp)) attempt.SignedRawTx = rlp.Bytes() - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast require.NoError(t, borm.InsertEthTxAttempt(&attempt)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) return etx } -func MustInsertUnconfrimedEthTxWithAttemptState(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address, txAttemptState bulletprooftxmanager.EthTxAttemptState, opts ...interface{}) bulletprooftxmanager.EthTx { +func MustInsertUnconfrimedEthTxWithAttemptState(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address, txAttemptState txmgr.EthTxAttemptState, opts ...interface{}) txmgr.EthTx { etx := MustInsertUnconfirmedEthTx(t, borm, nonce, fromAddress, opts...) attempt := NewLegacyEthTxAttempt(t, etx.ID) @@ -195,7 +196,7 @@ func MustInsertUnconfrimedEthTxWithAttemptState(t *testing.T, borm bulletprooftx return etx } -func MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) bulletprooftxmanager.EthTx { +func MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address, opts ...interface{}) txmgr.EthTx { etx := MustInsertUnconfirmedEthTx(t, borm, nonce, fromAddress, opts...) attempt := NewDynamicFeeEthTxAttempt(t, etx.ID) @@ -215,21 +216,21 @@ func MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t *testing.T, borm require.NoError(t, tx.EncodeRLP(rlp)) attempt.SignedRawTx = rlp.Bytes() - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast require.NoError(t, borm.InsertEthTxAttempt(&attempt)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) return etx } -func MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address) bulletprooftxmanager.EthTx { +func MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address) txmgr.EthTx { timeNow := time.Now() etx := NewEthTx(t, fromAddress) etx.BroadcastAt = &timeNow n := nonce etx.Nonce = &n - etx.State = bulletprooftxmanager.EthTxUnconfirmed + etx.State = txmgr.EthTxUnconfirmed require.NoError(t, borm.InsertEthTx(&etx)) attempt := NewLegacyEthTxAttempt(t, etx.ID) @@ -238,49 +239,66 @@ func MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t *testing.T, borm bul require.NoError(t, tx.EncodeRLP(rlp)) attempt.SignedRawTx = rlp.Bytes() - attempt.State = bulletprooftxmanager.EthTxAttemptInsufficientEth + attempt.State = txmgr.EthTxAttemptInsufficientEth require.NoError(t, borm.InsertEthTxAttempt(&attempt)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) return etx } -func MustInsertConfirmedEthTxWithLegacyAttempt(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, broadcastBeforeBlockNum int64, fromAddress common.Address) bulletprooftxmanager.EthTx { +func MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t *testing.T, borm txmgr.ORM, nonce int64, broadcastBeforeBlockNum int64, + broadcastAt time.Time, fromAddress common.Address) txmgr.EthTx { + etx := NewEthTx(t, fromAddress) + + etx.BroadcastAt = &broadcastAt + etx.Nonce = &nonce + etx.State = txmgr.EthTxConfirmedMissingReceipt + require.NoError(t, borm.InsertEthTx(&etx)) + attempt := NewLegacyEthTxAttempt(t, etx.ID) + attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum + attempt.State = txmgr.EthTxAttemptBroadcast + require.NoError(t, borm.InsertEthTxAttempt(&attempt)) + etx.EthTxAttempts = append(etx.EthTxAttempts, attempt) + return etx +} + +func MustInsertConfirmedEthTxWithLegacyAttempt(t *testing.T, borm txmgr.ORM, nonce int64, broadcastBeforeBlockNum int64, fromAddress common.Address) txmgr.EthTx { timeNow := time.Now() etx := NewEthTx(t, fromAddress) etx.BroadcastAt = &timeNow etx.Nonce = &nonce - etx.State = bulletprooftxmanager.EthTxConfirmed + etx.State = txmgr.EthTxConfirmed require.NoError(t, borm.InsertEthTx(&etx)) attempt := NewLegacyEthTxAttempt(t, etx.ID) attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast require.NoError(t, borm.InsertEthTxAttempt(&attempt)) etx.EthTxAttempts = append(etx.EthTxAttempts, attempt) return etx } -func MustInsertInProgressEthTxWithAttempt(t *testing.T, borm bulletprooftxmanager.ORM, nonce int64, fromAddress common.Address) bulletprooftxmanager.EthTx { +func MustInsertInProgressEthTxWithAttempt(t *testing.T, borm txmgr.ORM, nonce int64, fromAddress common.Address) txmgr.EthTx { etx := NewEthTx(t, fromAddress) etx.BroadcastAt = nil etx.Nonce = &nonce - etx.State = bulletprooftxmanager.EthTxInProgress + etx.State = txmgr.EthTxInProgress require.NoError(t, borm.InsertEthTx(&etx)) attempt := NewLegacyEthTxAttempt(t, etx.ID) tx := types.NewTransaction(uint64(nonce), testutils.NewAddress(), big.NewInt(142), 242, big.NewInt(342), []byte{1, 2, 3}) rlp := new(bytes.Buffer) require.NoError(t, tx.EncodeRLP(rlp)) attempt.SignedRawTx = rlp.Bytes() - attempt.State = bulletprooftxmanager.EthTxAttemptInProgress + attempt.State = txmgr.EthTxAttemptInProgress require.NoError(t, borm.InsertEthTxAttempt(&attempt)) etx, err := borm.FindEthTxWithAttempts(etx.ID) require.NoError(t, err) return etx } -func MustInsertUnstartedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, fromAddress common.Address, opts ...interface{}) bulletprooftxmanager.EthTx { +func MustInsertUnstartedEthTx(t *testing.T, borm txmgr.ORM, fromAddress common.Address, opts ...interface{}) txmgr.EthTx { var subject uuid.NullUUID for _, opt := range opts { switch v := opt.(type) { @@ -289,15 +307,15 @@ func MustInsertUnstartedEthTx(t *testing.T, borm bulletprooftxmanager.ORM, fromA } } etx := NewEthTx(t, fromAddress) - etx.State = bulletprooftxmanager.EthTxUnstarted + etx.State = txmgr.EthTxUnstarted etx.Subject = subject require.NoError(t, borm.InsertEthTx(&etx)) return etx } -func NewLegacyEthTxAttempt(t *testing.T, etxID int64) bulletprooftxmanager.EthTxAttempt { +func NewLegacyEthTxAttempt(t *testing.T, etxID int64) txmgr.EthTxAttempt { gasPrice := utils.NewBig(big.NewInt(1)) - return bulletprooftxmanager.EthTxAttempt{ + return txmgr.EthTxAttempt{ ChainSpecificGasLimit: 42, EthTxID: etxID, GasPrice: gasPrice, @@ -305,14 +323,14 @@ func NewLegacyEthTxAttempt(t *testing.T, etxID int64) bulletprooftxmanager.EthTx // Ignore all actual values SignedRawTx: hexutil.MustDecode("0xf889808504a817c8008307a12094000000000000000000000000000000000000000080a400000000000000000000000000000000000000000000000000000000000000000000000025a0838fe165906e2547b9a052c099df08ec891813fea4fcdb3c555362285eb399c5a070db99322490eb8a0f2270be6eca6e3aedbc49ff57ef939cf2774f12d08aa85e"), Hash: utils.NewHash(), - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, } } -func NewDynamicFeeEthTxAttempt(t *testing.T, etxID int64) bulletprooftxmanager.EthTxAttempt { +func NewDynamicFeeEthTxAttempt(t *testing.T, etxID int64) txmgr.EthTxAttempt { gasTipCap := utils.NewBig(big.NewInt(1)) gasFeeCap := utils.NewBig(big.NewInt(1)) - return bulletprooftxmanager.EthTxAttempt{ + return txmgr.EthTxAttempt{ TxType: 0x2, EthTxID: etxID, GasTipCap: gasTipCap, @@ -321,12 +339,12 @@ func NewDynamicFeeEthTxAttempt(t *testing.T, etxID int64) bulletprooftxmanager.E // Ignore all actual values SignedRawTx: hexutil.MustDecode("0xf889808504a817c8008307a12094000000000000000000000000000000000000000080a400000000000000000000000000000000000000000000000000000000000000000000000025a0838fe165906e2547b9a052c099df08ec891813fea4fcdb3c555362285eb399c5a070db99322490eb8a0f2270be6eca6e3aedbc49ff57ef939cf2774f12d08aa85e"), Hash: utils.NewHash(), - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, ChainSpecificGasLimit: 42, } } -func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHash common.Hash) bulletprooftxmanager.EthReceipt { +func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.EthReceipt { transactionIndex := uint(NewRandomInt64()) receipt := evmtypes.Receipt{ @@ -338,7 +356,7 @@ func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHas data, err := json.Marshal(receipt) require.NoError(t, err) - r := bulletprooftxmanager.EthReceipt{ + r := txmgr.EthReceipt{ BlockNumber: blockNumber, BlockHash: blockHash, TxHash: txHash, @@ -348,22 +366,22 @@ func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHas return r } -func MustInsertEthReceipt(t *testing.T, borm bulletprooftxmanager.ORM, blockNumber int64, blockHash common.Hash, txHash common.Hash) bulletprooftxmanager.EthReceipt { +func MustInsertEthReceipt(t *testing.T, borm txmgr.ORM, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.EthReceipt { r := NewEthReceipt(t, blockNumber, blockHash, txHash) require.NoError(t, borm.InsertEthReceipt(&r)) return r } -func MustInsertConfirmedEthTxWithReceipt(t *testing.T, borm bulletprooftxmanager.ORM, fromAddress common.Address, nonce, blockNum int64) (etx bulletprooftxmanager.EthTx) { +func MustInsertConfirmedEthTxWithReceipt(t *testing.T, borm txmgr.ORM, fromAddress common.Address, nonce, blockNum int64) (etx txmgr.EthTx) { etx = MustInsertConfirmedEthTxWithLegacyAttempt(t, borm, nonce, blockNum, fromAddress) MustInsertEthReceipt(t, borm, blockNum, utils.NewHash(), etx.EthTxAttempts[0].Hash) return etx } -func MustInsertFatalErrorEthTx(t *testing.T, borm bulletprooftxmanager.ORM, fromAddress common.Address) bulletprooftxmanager.EthTx { +func MustInsertFatalErrorEthTx(t *testing.T, borm txmgr.ORM, fromAddress common.Address) txmgr.EthTx { etx := NewEthTx(t, fromAddress) etx.Error = null.StringFrom("something exploded") - etx.State = bulletprooftxmanager.EthTxFatalError + etx.State = txmgr.EthTxFatalError require.NoError(t, borm.InsertEthTx(&etx)) return etx @@ -466,13 +484,13 @@ func MustInsertV2JobSpec(t *testing.T, db *sqlx.DB, transmitterAddress common.Ad oracleSpec := MustInsertOffchainreportingOracleSpec(t, db, addr) jb := job.Job{ - OffchainreportingOracleSpec: &oracleSpec, - OffchainreportingOracleSpecID: &oracleSpec.ID, - ExternalJobID: uuid.NewV4(), - Type: job.OffchainReporting, - SchemaVersion: 1, - PipelineSpec: &pipelineSpec, - PipelineSpecID: pipelineSpec.ID, + OCROracleSpec: &oracleSpec, + OCROracleSpecID: &oracleSpec.ID, + ExternalJobID: uuid.NewV4(), + Type: job.OffchainReporting, + SchemaVersion: 1, + PipelineSpec: &pipelineSpec, + PipelineSpecID: pipelineSpec.ID, } jorm := job.NewORM(db, nil, nil, nil, logger.TestLogger(t), NewTestGeneralConfig(t)) @@ -481,12 +499,12 @@ func MustInsertV2JobSpec(t *testing.T, db *sqlx.DB, transmitterAddress common.Ad return jb } -func MustInsertOffchainreportingOracleSpec(t *testing.T, db *sqlx.DB, transmitterAddress ethkey.EIP55Address) job.OffchainReportingOracleSpec { +func MustInsertOffchainreportingOracleSpec(t *testing.T, db *sqlx.DB, transmitterAddress ethkey.EIP55Address) job.OCROracleSpec { t.Helper() ocrKeyID := models.MustSha256HashFromHex(DefaultOCRKeyBundleID) - spec := job.OffchainReportingOracleSpec{} - require.NoError(t, db.Get(&spec, `INSERT INTO offchainreporting_oracle_specs (created_at, updated_at, contract_address, p2p_bootstrap_peers, is_bootstrap_peer, encrypted_ocr_key_bundle_id, transmitter_address, observation_timeout, blockchain_timeout, contract_config_tracker_subscribe_interval, contract_config_tracker_poll_interval, contract_config_confirmations, database_timeout, observation_grace_period, contract_transmitter_transmit_timeout) VALUES ( + spec := job.OCROracleSpec{} + require.NoError(t, db.Get(&spec, `INSERT INTO ocr_oracle_specs (created_at, updated_at, contract_address, p2p_bootstrap_peers, is_bootstrap_peer, encrypted_ocr_key_bundle_id, transmitter_address, observation_timeout, blockchain_timeout, contract_config_tracker_subscribe_interval, contract_config_tracker_poll_interval, contract_config_confirmations, database_timeout, observation_grace_period, contract_transmitter_transmit_timeout) VALUES ( NOW(),NOW(),$1,'{}',false,$2,$3,0,0,0,0,0,0,0,0 ) RETURNING *`, NewEIP55Address(), &ocrKeyID, &transmitterAddress)) return spec @@ -557,7 +575,7 @@ func MustInsertKeeperRegistry(t *testing.T, db *sqlx.DB, korm keeper.ORM, ethKey } func MustInsertUpkeepForRegistry(t *testing.T, db *sqlx.DB, cfg keeper.Config, registry keeper.Registry) keeper.UpkeepRegistration { - korm := keeper.NewORM(db, logger.TestLogger(t), cfg, bulletprooftxmanager.SendEveryStrategy{}) + korm := keeper.NewORM(db, logger.TestLogger(t), cfg, txmgr.SendEveryStrategy{}) upkeepID, err := korm.LowestUnsyncedID(registry.ID) require.NoError(t, err) upkeep := keeper.UpkeepRegistration{ diff --git a/core/internal/cltest/heavyweight/orm.go b/core/internal/cltest/heavyweight/orm.go index eef26862d91..1f1141944ca 100644 --- a/core/internal/cltest/heavyweight/orm.go +++ b/core/internal/cltest/heavyweight/orm.go @@ -14,25 +14,25 @@ import ( "runtime" "testing" + "github.com/smartcontractkit/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/store/dialects" migrations "github.com/smartcontractkit/chainlink/core/store/migrate" - "github.com/smartcontractkit/sqlx" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" ) // FullTestDB creates an DB which runs in a separate database than the normal // unit tests, so you can do things like use other Postgres connection types // with it. func FullTestDB(t *testing.T, name string, migrate bool, loadFixtures bool) (*configtest.TestGeneralConfig, *sqlx.DB) { - if testing.Short() { - t.Skip("skipping due to use of FullTestDB") - } + testutils.SkipShort(t, "FullTestDB") overrides := configtest.GeneralConfigOverrides{ SecretGenerator: cltest.MockSecretGenerator{}, } diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index ad03909d94c..dd9e059434c 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -374,6 +374,10 @@ func (m MockCookieAuthenticator) Authenticate(sessions.SessionRequest) (*http.Co return MustGenerateSessionCookie(m.t, m.SessionID), m.Error } +func (m MockCookieAuthenticator) Logout() error { + return nil +} + type MockSessionRequestBuilder struct { Count int Error error diff --git a/core/internal/cltest/simulated_backend.go b/core/internal/cltest/simulated_backend.go index 29b0290501e..73cbfa569ea 100644 --- a/core/internal/cltest/simulated_backend.go +++ b/core/internal/cltest/simulated_backend.go @@ -54,6 +54,11 @@ func NewApplicationWithConfigAndKeyOnSimulatedBlockchain( chainId := backend.Blockchain().Config().ChainID cfg.Overrides.DefaultChainID = chainId + // Only set P2PEnabled override to false if it wasn't set by calling test + if !cfg.Overrides.P2PEnabled.Valid { + cfg.Overrides.P2PEnabled = null.BoolFrom(false) + } + client := evmclient.NewSimulatedBackendClient(t, backend, chainId) eventBroadcaster := pg.NewEventBroadcaster(cfg.DatabaseURL(), 0, 0, logger.TestLogger(t), uuid.NewV4()) diff --git a/core/internal/features_ocr2_test.go b/core/internal/features_ocr2_test.go index 694995ada2f..e28fb4e10ae 100644 --- a/core/internal/features_ocr2_test.go +++ b/core/internal/features_ocr2_test.go @@ -1,6 +1,7 @@ package internal_test import ( + "bytes" "context" "encoding/json" "fmt" @@ -14,6 +15,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" @@ -36,11 +38,13 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" + "github.com/smartcontractkit/chainlink/core/services/relay/evm" "github.com/smartcontractkit/chainlink/core/store/models" ) @@ -86,6 +90,7 @@ func setupNodeOCR2(t *testing.T, owner *bind.TransactOpts, port uint16, dbName s config, _ := heavyweight.FullTestDB(t, fmt.Sprintf("%s%d", dbName, port), true, true) config.Overrides.FeatureOffchainReporting = null.BoolFrom(false) config.Overrides.FeatureOffchainReporting2 = null.BoolFrom(true) + config.Overrides.P2PEnabled = null.BoolFrom(true) config.Overrides.P2PNetworkingStack = ocrnetworking.NetworkingStackV2 config.Overrides.P2PListenPort = null.NewInt(0, true) config.Overrides.SetP2PV2DeltaDial(500 * time.Millisecond) @@ -176,7 +181,7 @@ func TestIntegration_OCR2(t *testing.T) { } }() - lggr.Debugw("Setting Payees on Oracle Contract", "transmitters", transmitters) + lggr.Debugw("Setting Payees on OraclePlugin Contract", "transmitters", transmitters) _, err := ocrContract.SetPayees( owner, transmitters, @@ -208,7 +213,7 @@ func TestIntegration_OCR2(t *testing.T) { require.NoError(t, err) b.Commit() - err = appBootstrap.Start() + err = appBootstrap.Start(testutils.Context(t)) require.NoError(t, err) defer appBootstrap.Stop() @@ -240,7 +245,7 @@ chainID = 1337 "0": {}, "10": {}, "20": {}, "30": {}, } for i := 0; i < 4; i++ { - err = apps[i].Start() + err = apps[i].Start(testutils.Context(t)) require.NoError(t, err) defer apps[i].Stop() @@ -267,21 +272,22 @@ chainID = 1337 defer servers[i].Close() u, _ := url.Parse(servers[i].URL) apps[i].BridgeORM().CreateBridgeType(&bridges.BridgeType{ - Name: bridges.TaskType(fmt.Sprintf("bridge%d", i)), + Name: bridges.BridgeName(fmt.Sprintf("bridge%d", i)), URL: models.WebURL(*u), }) - ocrJob, err := offchainreporting2.ValidatedOracleSpecToml(apps[i].Config, fmt.Sprintf(` + ocrJob, err := validate.ValidatedOracleSpecToml(apps[i].Config, fmt.Sprintf(` type = "offchainreporting2" -relay = "evm" +relay = "evm" schemaVersion = 1 +pluginType = "median" name = "web oracle spec" -contractID = "%s" -ocrKeyBundleID = "%s" -transmitterID = "%s" +contractID = "%s" +ocrKeyBundleID = "%s" +transmitterID = "%s" contractConfigConfirmations = 1 contractConfigTrackerPollInterval = "1s" -observationSource = """ +observationSource = """ // data source 1 ds1 [type=bridge name="%s"]; ds1_parse [type=jsonparse path="data"]; @@ -297,6 +303,9 @@ observationSource = """ answer1 [type=median index=0]; """ +[relayConfig] +chainID = 1337 +[pluginConfig] juelsPerFeeCoinSource = """ // data source 1 ds1 [type=bridge name="%s"]; @@ -313,8 +322,6 @@ juelsPerFeeCoinSource = """ answer1 [type=median index=0]; """ -[relayConfig] -chainID = 1337 `, ocrContractAddress, kbs[i].ID(), transmitters[i], fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i, fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i)) require.NoError(t, err) err = apps[i].AddJobV2(context.Background(), &ocrJob) @@ -362,4 +369,17 @@ chainID = 1337 } } assert.Len(t, expectedMeta, 0, "expected metadata %v", expectedMeta) + + // Assert we can read the latest config digest and epoch after a report has been submitted. + contractABI, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) + require.NoError(t, err) + ct := evm.NewOCRContractTransmitter(ocrContractAddress, apps[0].Chains.EVM.Chains()[0].Client(), contractABI, nil, lggr) + configDigest, epoch, err := ct.LatestConfigDigestAndEpoch(context.Background()) + require.NoError(t, err) + details, err := ocrContract.LatestConfigDetails(nil) + require.NoError(t, err) + assert.True(t, bytes.Equal(configDigest[:], details.ConfigDigest[:])) + digestAndEpoch, err := ocrContract.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err) + assert.Equal(t, digestAndEpoch.Epoch, epoch) } diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 7bd36a3cd38..e696051e8b3 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -16,9 +16,6 @@ import ( "testing" "time" - ocrcommontypes "github.com/smartcontractkit/libocr/commontypes" - ocrnetworking "github.com/smartcontractkit/libocr/networking" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" @@ -29,6 +26,18 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/onsi/gomega" uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + + ocrcommontypes "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + "github.com/smartcontractkit/libocr/gethwrappers/testoffchainaggregator" + ocrnetworking "github.com/smartcontractkit/libocr/networking" + "github.com/smartcontractkit/libocr/offchainreporting/confighelper" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" @@ -41,13 +50,14 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/multiwordconsumer_wrapper" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/operator_wrapper" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ocrkey" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/webhook" "github.com/smartcontractkit/chainlink/core/static" @@ -55,14 +65,6 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" webauth "github.com/smartcontractkit/chainlink/core/web/auth" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - "github.com/smartcontractkit/libocr/gethwrappers/testoffchainaggregator" - "github.com/smartcontractkit/libocr/offchainreporting/confighelper" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" ) var oneETH = assets.Eth(*big.NewInt(1000000000000000000)) @@ -70,15 +72,14 @@ var oneETH = assets.Eth(*big.NewInt(1000000000000000000)) func TestIntegration_ExternalInitiatorV2(t *testing.T) { t.Parallel() - ethClient, _, assertMockCalls := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.FeatureExternalInitiators = null.BoolFrom(true) cfg.Overrides.SetTriggerFallbackDBPollInterval(10 * time.Millisecond) app := cltest.NewApplicationWithConfig(t, cfg, ethClient, cltest.UseRealExternalInitiatorManager) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) var ( eiName = "substrate-ei" @@ -163,7 +164,7 @@ func TestIntegration_ExternalInitiatorV2(t *testing.T) { })) u, _ := url.Parse(bridgeServer.URL) err := app.BridgeORM().CreateBridgeType(&bridges.BridgeType{ - Name: bridges.TaskType("substrate-adapter1"), + Name: bridges.BridgeName("substrate-adapter1"), URL: models.WebURL(*u), }) require.NoError(t, err) @@ -246,10 +247,10 @@ observationSource = """ func TestIntegration_AuthToken(t *testing.T) { t.Parallel() - ethClient, _, assertMockCalls := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) + app := cltest.NewApplication(t, ethClient) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // set up user mockUser := cltest.MustRandomUser(t) @@ -369,7 +370,7 @@ func TestIntegration_DirectRequest(t *testing.T) { require.NoError(t, err) b.Commit() - err = app.Start() + err = app.Start(testutils.Context(t)) require.NoError(t, err) mockServerUSD := cltest.NewHTTPMockServer(t, 200, "GET", `{"USD": 614.64}`) @@ -497,6 +498,7 @@ func setupNode(t *testing.T, owner *bind.TransactOpts, portV1, portV2 int, dbNam config.Overrides.Dev = null.BoolFrom(true) // Disables ocr spec validation so we can have fast polling for the test. config.Overrides.FeatureOffchainReporting = null.BoolFrom(true) config.Overrides.FeatureOffchainReporting2 = null.BoolFrom(true) + config.Overrides.P2PEnabled = null.BoolFrom(true) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, b) _, err := app.GetKeyStore().P2P().Create() @@ -550,9 +552,7 @@ func setupNode(t *testing.T, owner *bind.TransactOpts, portV1, portV2 int, dbNam } func TestIntegration_OCR(t *testing.T) { - if testing.Short() { - t.Skip() - } + testutils.SkipShort(t, "long test") tests := []struct { id int portStart int // Test need to run in parallel, all need distinct port ranges. @@ -640,10 +640,10 @@ func TestIntegration_OCR(t *testing.T) { require.NoError(t, err) b.Commit() - err = appBootstrap.Start() + err = appBootstrap.Start(testutils.Context(t)) require.NoError(t, err) - jb, err := offchainreporting.ValidatedOracleSpecToml(appBootstrap.GetChains().EVM, fmt.Sprintf(` + jb, err := ocr.ValidatedOracleSpecToml(appBootstrap.GetChains().EVM, fmt.Sprintf(` type = "offchainreporting" schemaVersion = 1 name = "boot" @@ -676,7 +676,7 @@ isBootstrapPeer = true "0": {}, "10": {}, "20": {}, "30": {}, } for i := 0; i < numOracles; i++ { - err = apps[i].Start() + err = apps[i].Start(testutils.Context(t)) require.NoError(t, err) // Since this API speed is > ObservationTimeout we should ignore it and still produce values. @@ -702,14 +702,14 @@ isBootstrapPeer = true defer servers[i].Close() u, _ := url.Parse(servers[i].URL) err := apps[i].BridgeORM().CreateBridgeType(&bridges.BridgeType{ - Name: bridges.TaskType(fmt.Sprintf("bridge%d", i)), + Name: bridges.BridgeName(fmt.Sprintf("bridge%d", i)), URL: models.WebURL(*u), }) require.NoError(t, err) // Note we need: observationTimeout + observationGracePeriod + DeltaGrace (500ms) < DeltaRound (1s) // So 200ms + 200ms + 500ms < 1s - jb, err := offchainreporting.ValidatedOracleSpecToml(apps[i].GetChains().EVM, fmt.Sprintf(` + jb, err := ocr.ValidatedOracleSpecToml(apps[i].GetChains().EVM, fmt.Sprintf(` type = "offchainreporting" schemaVersion = 1 name = "web oracle spec" @@ -795,8 +795,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.GlobalBalanceMonitorEnabled = null.BoolFrom(false) - ethClient, sub, assertMocksCalled := cltest.NewEthMocksWithDefaultChain(t) - defer assertMocksCalled() + ethClient, sub := cltest.NewEthMocksWithDefaultChain(t) chchNewHeads := make(chan chan<- *evmtypes.Head, 1) db := pgtest.NewSqlxDB(t) @@ -845,19 +844,19 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { ethClient.On("HeadByNumber", mock.Anything, mock.AnythingOfType("*big.Int")).Return(&h42, nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && - b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == "0x29" && - b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == "0x2a" + b[0].Method == "eth_getBlockByNumber" && b[0].Args[0] == "0x2a" && + b[1].Method == "eth_getBlockByNumber" && b[1].Args[0] == "0x29" })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &b41 - elems[1].Result = &b42 + elems[0].Result = &b42 + elems[1].Result = &b41 }) ethClient.On("Dial", mock.Anything).Return(nil) ethClient.On("ChainID", mock.Anything).Return(cfg.DefaultChainID(), nil) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(oneETH.ToInt(), nil) - require.NoError(t, cc.Start()) + require.NoError(t, cc.Start(testutils.Context(t))) var newHeads chan<- *evmtypes.Head select { case newHeads = <-chchNewHeads: diff --git a/core/internal/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go b/core/internal/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go new file mode 100644 index 00000000000..0755d22f271 --- /dev/null +++ b/core/internal/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go @@ -0,0 +1,253 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package batch_blockhash_store + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +var BatchBlockhashStoreMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"blockhashStoreAddr\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BHS\",\"outputs\":[{\"internalType\":\"contractBlockhashStore\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"blockNumbers\",\"type\":\"uint256[]\"}],\"name\":\"getBlockhashes\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"blockNumbers\",\"type\":\"uint256[]\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"blockNumbers\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes[]\",\"name\":\"headers\",\"type\":\"bytes[]\"}],\"name\":\"storeVerifyHeader\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b50604051610acb380380610acb83398101604081905261002f91610044565b60601b6001600160601b031916608052610074565b60006020828403121561005657600080fd5b81516001600160a01b038116811461006d57600080fd5b9392505050565b60805160601c610a256100a66000396000818160a7015281816101270152818161023a01526104290152610a256000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806306bd010d146100515780631f600f86146100665780635d290e211461008f578063f745eafb146100a2575b600080fd5b61006461005f36600461059e565b6100ee565b005b61007961007436600461059e565b6101e2565b6040516100869190610749565b60405180910390f35b61006461009d3660046105db565b6103ac565b6100c97f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610086565b60005b81518110156101de5761011c82828151811061010f5761010f6108f6565b60200260200101516104fe565b610125576101cc565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16636057361d838381518110610173576101736108f6565b60200260200101516040518263ffffffff1660e01b815260040161019991815260200190565b600060405180830381600087803b1580156101b357600080fd5b505af11580156101c7573d6000803e3d6000fd5b505050505b806101d68161088e565b9150506100f1565b5050565b60606000825167ffffffffffffffff81111561020057610200610925565b604051908082528060200260200182016040528015610229578160200160208202803683370190505b50905060005b83518110156103a5577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663e9413d38858381518110610286576102866108f6565b60200260200101516040518263ffffffff1660e01b81526004016102ac91815260200190565b60206040518083038186803b1580156102c457600080fd5b505afa925050508015610312575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261030f91810190610730565b60015b6103725761031e610954565b806308c379a014156103665750610333610970565b8061033e5750610368565b6000801b838381518110610354576103546108f6565b60200260200101818152505050610393565b505b3d6000803e3d6000fd5b80838381518110610385576103856108f6565b602002602001018181525050505b8061039d8161088e565b91505061022f565b5092915050565b805182511461041b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f696e70757420617272617920617267206c656e67746873206d69736d61746368604482015260640160405180910390fd5b60005b82518110156104f9577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fadff0e1848381518110610475576104756108f6565b602002602001015184848151811061048f5761048f6108f6565b60200260200101516040518363ffffffff1660e01b81526004016104b492919061078d565b600060405180830381600087803b1580156104ce57600080fd5b505af11580156104e2573d6000803e3d6000fd5b5050505080806104f19061088e565b91505061041e565b505050565b600061010043111561051e576105166101004361082c565b821015610521565b60015b92915050565b600082601f83011261053857600080fd5b8135602061054582610808565b6040516105528282610843565b8381528281019150858301600585901b8701840188101561057257600080fd5b60005b8581101561059157813584529284019290840190600101610575565b5090979650505050505050565b6000602082840312156105b057600080fd5b813567ffffffffffffffff8111156105c757600080fd5b6105d384828501610527565b949350505050565b60008060408084860312156105ef57600080fd5b833567ffffffffffffffff8082111561060757600080fd5b61061387838801610527565b945060209150818601358181111561062a57600080fd5b8601601f8101881361063b57600080fd5b803561064681610808565b85516106528282610843565b8281528581019150838601600584901b850187018c101561067257600080fd5b60005b8481101561071e5781358781111561068c57600080fd5b8601603f81018e1361069d57600080fd5b88810135888111156106b1576106b1610925565b8a516106e48b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160182610843565b8181528f8c8385010111156106f857600080fd5b818c84018c83013760009181018b01919091528552509287019290870190600101610675565b50989b909a5098505050505050505050565b60006020828403121561074257600080fd5b5051919050565b6020808252825182820181905260009190848201906040850190845b8181101561078157835183529284019291840191600101610765565b50909695505050505050565b82815260006020604081840152835180604085015260005b818110156107c1578581018301518582016060015282016107a5565b818111156107d3576000606083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201606001949350505050565b600067ffffffffffffffff82111561082257610822610925565b5060051b60200190565b60008282101561083e5761083e6108c7565b500390565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f830116810181811067ffffffffffffffff8211171561088757610887610925565b6040525050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156108c0576108c06108c7565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060033d111561096d5760046000803e5060005160e01c5b90565b600060443d101561097e5790565b6040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc803d016004833e81513d67ffffffffffffffff81602484011181841117156109cc57505050505090565b82850191508151818111156109e45750505050505090565b843d87010160208285010111156109fe5750505050505090565b610a0d60208286010187610843565b50909594505050505056fea164736f6c6343000806000a", +} + +var BatchBlockhashStoreABI = BatchBlockhashStoreMetaData.ABI + +var BatchBlockhashStoreBin = BatchBlockhashStoreMetaData.Bin + +func DeployBatchBlockhashStore(auth *bind.TransactOpts, backend bind.ContractBackend, blockhashStoreAddr common.Address) (common.Address, *types.Transaction, *BatchBlockhashStore, error) { + parsed, err := BatchBlockhashStoreMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(BatchBlockhashStoreBin), backend, blockhashStoreAddr) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &BatchBlockhashStore{BatchBlockhashStoreCaller: BatchBlockhashStoreCaller{contract: contract}, BatchBlockhashStoreTransactor: BatchBlockhashStoreTransactor{contract: contract}, BatchBlockhashStoreFilterer: BatchBlockhashStoreFilterer{contract: contract}}, nil +} + +type BatchBlockhashStore struct { + address common.Address + abi abi.ABI + BatchBlockhashStoreCaller + BatchBlockhashStoreTransactor + BatchBlockhashStoreFilterer +} + +type BatchBlockhashStoreCaller struct { + contract *bind.BoundContract +} + +type BatchBlockhashStoreTransactor struct { + contract *bind.BoundContract +} + +type BatchBlockhashStoreFilterer struct { + contract *bind.BoundContract +} + +type BatchBlockhashStoreSession struct { + Contract *BatchBlockhashStore + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type BatchBlockhashStoreCallerSession struct { + Contract *BatchBlockhashStoreCaller + CallOpts bind.CallOpts +} + +type BatchBlockhashStoreTransactorSession struct { + Contract *BatchBlockhashStoreTransactor + TransactOpts bind.TransactOpts +} + +type BatchBlockhashStoreRaw struct { + Contract *BatchBlockhashStore +} + +type BatchBlockhashStoreCallerRaw struct { + Contract *BatchBlockhashStoreCaller +} + +type BatchBlockhashStoreTransactorRaw struct { + Contract *BatchBlockhashStoreTransactor +} + +func NewBatchBlockhashStore(address common.Address, backend bind.ContractBackend) (*BatchBlockhashStore, error) { + abi, err := abi.JSON(strings.NewReader(BatchBlockhashStoreABI)) + if err != nil { + return nil, err + } + contract, err := bindBatchBlockhashStore(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &BatchBlockhashStore{address: address, abi: abi, BatchBlockhashStoreCaller: BatchBlockhashStoreCaller{contract: contract}, BatchBlockhashStoreTransactor: BatchBlockhashStoreTransactor{contract: contract}, BatchBlockhashStoreFilterer: BatchBlockhashStoreFilterer{contract: contract}}, nil +} + +func NewBatchBlockhashStoreCaller(address common.Address, caller bind.ContractCaller) (*BatchBlockhashStoreCaller, error) { + contract, err := bindBatchBlockhashStore(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &BatchBlockhashStoreCaller{contract: contract}, nil +} + +func NewBatchBlockhashStoreTransactor(address common.Address, transactor bind.ContractTransactor) (*BatchBlockhashStoreTransactor, error) { + contract, err := bindBatchBlockhashStore(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &BatchBlockhashStoreTransactor{contract: contract}, nil +} + +func NewBatchBlockhashStoreFilterer(address common.Address, filterer bind.ContractFilterer) (*BatchBlockhashStoreFilterer, error) { + contract, err := bindBatchBlockhashStore(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &BatchBlockhashStoreFilterer{contract: contract}, nil +} + +func bindBatchBlockhashStore(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(BatchBlockhashStoreABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +func (_BatchBlockhashStore *BatchBlockhashStoreRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BatchBlockhashStore.Contract.BatchBlockhashStoreCaller.contract.Call(opts, result, method, params...) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.BatchBlockhashStoreTransactor.contract.Transfer(opts) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.BatchBlockhashStoreTransactor.contract.Transact(opts, method, params...) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BatchBlockhashStore.Contract.contract.Call(opts, result, method, params...) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.contract.Transfer(opts) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.contract.Transact(opts, method, params...) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreCaller) BHS(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _BatchBlockhashStore.contract.Call(opts, &out, "BHS") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_BatchBlockhashStore *BatchBlockhashStoreSession) BHS() (common.Address, error) { + return _BatchBlockhashStore.Contract.BHS(&_BatchBlockhashStore.CallOpts) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreCallerSession) BHS() (common.Address, error) { + return _BatchBlockhashStore.Contract.BHS(&_BatchBlockhashStore.CallOpts) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreCaller) GetBlockhashes(opts *bind.CallOpts, blockNumbers []*big.Int) ([][32]byte, error) { + var out []interface{} + err := _BatchBlockhashStore.contract.Call(opts, &out, "getBlockhashes", blockNumbers) + + if err != nil { + return *new([][32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([][32]byte)).(*[][32]byte) + + return out0, err + +} + +func (_BatchBlockhashStore *BatchBlockhashStoreSession) GetBlockhashes(blockNumbers []*big.Int) ([][32]byte, error) { + return _BatchBlockhashStore.Contract.GetBlockhashes(&_BatchBlockhashStore.CallOpts, blockNumbers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreCallerSession) GetBlockhashes(blockNumbers []*big.Int) ([][32]byte, error) { + return _BatchBlockhashStore.Contract.GetBlockhashes(&_BatchBlockhashStore.CallOpts, blockNumbers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactor) Store(opts *bind.TransactOpts, blockNumbers []*big.Int) (*types.Transaction, error) { + return _BatchBlockhashStore.contract.Transact(opts, "store", blockNumbers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreSession) Store(blockNumbers []*big.Int) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.Store(&_BatchBlockhashStore.TransactOpts, blockNumbers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactorSession) Store(blockNumbers []*big.Int) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.Store(&_BatchBlockhashStore.TransactOpts, blockNumbers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactor) StoreVerifyHeader(opts *bind.TransactOpts, blockNumbers []*big.Int, headers [][]byte) (*types.Transaction, error) { + return _BatchBlockhashStore.contract.Transact(opts, "storeVerifyHeader", blockNumbers, headers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreSession) StoreVerifyHeader(blockNumbers []*big.Int, headers [][]byte) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.StoreVerifyHeader(&_BatchBlockhashStore.TransactOpts, blockNumbers, headers) +} + +func (_BatchBlockhashStore *BatchBlockhashStoreTransactorSession) StoreVerifyHeader(blockNumbers []*big.Int, headers [][]byte) (*types.Transaction, error) { + return _BatchBlockhashStore.Contract.StoreVerifyHeader(&_BatchBlockhashStore.TransactOpts, blockNumbers, headers) +} + +func (_BatchBlockhashStore *BatchBlockhashStore) Address() common.Address { + return _BatchBlockhashStore.address +} + +type BatchBlockhashStoreInterface interface { + BHS(opts *bind.CallOpts) (common.Address, error) + + GetBlockhashes(opts *bind.CallOpts, blockNumbers []*big.Int) ([][32]byte, error) + + Store(opts *bind.TransactOpts, blockNumbers []*big.Int) (*types.Transaction, error) + + StoreVerifyHeader(opts *bind.TransactOpts, blockNumbers []*big.Int, headers [][]byte) (*types.Transaction, error) + + Address() common.Address +} diff --git a/core/internal/gethwrappers/generated/derived_price_feed_wrapper/derived_price_feed_wrapper.go b/core/internal/gethwrappers/generated/derived_price_feed_wrapper/derived_price_feed_wrapper.go index 79e431ae8fa..724c8bf0193 100644 --- a/core/internal/gethwrappers/generated/derived_price_feed_wrapper/derived_price_feed_wrapper.go +++ b/core/internal/gethwrappers/generated/derived_price_feed_wrapper/derived_price_feed_wrapper.go @@ -5,7 +5,6 @@ package derived_price_feed_wrapper import ( "errors" - "fmt" "math/big" "strings" @@ -15,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" - "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated" ) var ( @@ -30,8 +28,8 @@ var ( ) var DerivedPriceFeedMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_base\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_quote\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"_decimals\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int256\",\"name\":\"current\",\"type\":\"int256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"}],\"name\":\"AnswerUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"startedBy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"}],\"name\":\"NewRound\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"base\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"description\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"}],\"name\":\"getRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"roundId\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"answer\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"answeredInRound\",\"type\":\"uint80\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"quote\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b506040516107c43803806107c48339818101604052606081101561003357600080fd5b508051602082015160409092015190919060ff81161580159061005a5750601260ff821611155b61009f576040805162461bcd60e51b8152602060048201526011602482015270496e76616c6964205f646563696d616c7360781b604482015290519081900360640190fd5b6000805460ff90921660ff19909216919091179055600780546001600160a01b039384166001600160a01b031991821617909155600880549290931691161790556106d5806100ef6000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c80638205bf6a11610081578063b5ab58dc1161005b578063b5ab58dc14610252578063b633620c1461026f578063feaf968c1461028c576100d4565b80638205bf6a146101cf578063999b93af146101d75780639a6fc8f5146101df576100d4565b806354fd4d50116100b257806354fd4d5014610142578063668a0f021461014a5780637284e41614610152576100d4565b8063313ce567146100d95780635001f3b5146100f757806350d25bcd14610128575b600080fd5b6100e1610294565b6040805160ff9092168252519081900360200190f35b6100ff61029d565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b6101306102b9565b60408051918252519081900360200190f35b6101306102bf565b6101306102c4565b61015a6102ca565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561019457818101518382015260200161017c565b50505050905090810190601f1680156101c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610130610301565b6100ff610307565b610208600480360360208110156101f557600080fd5b503569ffffffffffffffffffff16610323565b604051808669ffffffffffffffffffff1681526020018581526020018481526020018381526020018269ffffffffffffffffffff1681526020019550505050505060405180910390f35b6101306004803603602081101561026857600080fd5b503561037c565b6101306004803603602081101561028557600080fd5b503561038e565b6102086103a0565b60005460ff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b600081565b60035481565b60408051808201909152601481527f446572697665645072696365466565642e736f6c000000000000000000000000602082015290565b60025481565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b60008060008060006040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806106a26027913960400191505060405180910390fd5b60046020526000908152604090205481565b60056020526000908152604090205481565b600754600854600080549092839283928392839283926103dd9273ffffffffffffffffffffffffffffffffffffffff90811692169060ff166103f0565b9096909550429450849350600092509050565b6000808260ff16600a0a905060008573ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561044457600080fd5b505afa158015610458573d6000803e3d6000fd5b505050506040513d60a081101561046e57600080fd5b50602090810151604080517f313ce567000000000000000000000000000000000000000000000000000000008152905191935060009273ffffffffffffffffffffffffffffffffffffffff8a169263313ce567926004808201939291829003018186803b1580156104de57600080fd5b505afa1580156104f2573d6000803e3d6000fd5b505050506040513d602081101561050857600080fd5b50519050610517828287610651565b915060008673ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561056157600080fd5b505afa158015610575573d6000803e3d6000fd5b505050506040513d60a081101561058b57600080fd5b50602090810151604080517f313ce567000000000000000000000000000000000000000000000000000000008152905191935060009273ffffffffffffffffffffffffffffffffffffffff8b169263313ce567926004808201939291829003018186803b1580156105fb57600080fd5b505afa15801561060f573d6000803e3d6000fd5b505050506040513d602081101561062557600080fd5b50519050610634828289610651565b9150818585028161064157fe5b05955050505050505b9392505050565b60008160ff168360ff161015610672575060ff82820316600a0a830261064a565b8160ff168360ff1611156106995781830360ff16600a0a848161069157fe5b05905061064a565b50919291505056fe6e6f7420696d706c656d656e746564202d20757365206c6174657374526f756e64446174612829a164736f6c6343000706000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_base\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_quote\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"_decimals\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BASE\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DECIMALS\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"QUOTE\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"description\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"}],\"name\":\"getRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"\",\"type\":\"uint80\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"roundId\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"answer\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"answeredInRound\",\"type\":\"uint80\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60e060405234801561001057600080fd5b50604051610c14380380610c1483398101604081905261002f916100ec565b60ff8116158015906100455750601260ff821611155b6100895760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964205f646563696d616c7360781b604482015260640160405180910390fd5b60f81b7fff000000000000000000000000000000000000000000000000000000000000001660c052606091821b6001600160601b0319908116608052911b1660a052610139565b80516001600160a01b03811681146100e757600080fd5b919050565b60008060006060848603121561010157600080fd5b61010a846100d0565b9250610118602085016100d0565b9150604084015160ff8116811461012e57600080fd5b809150509250925092565b60805160601c60a05160601c60c05160f81c610a6d6101a76000396000818160920152818160cd0152818161041a0152818161058f01526105bd0152600081816101950152818161044401526104ea0152600081816101e1015281816102cf01526103750152610a6d6000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c80639a6fc8f51161005b5780639a6fc8f5146101465780639c57983914610190578063ec342ad0146101dc578063feaf968c1461020357600080fd5b80632e0f26251461008d578063313ce567146100cb57806354fd4d50146100f15780637284e41614610107575b600080fd5b6100b47f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020015b60405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000006100b4565b6100f9600081565b6040519081526020016100c2565b604080518082018252601481527f446572697665645072696365466565642e736f6c000000000000000000000000602082015290516100c2919061070c565b610159610154366004610674565b61020b565b6040805169ffffffffffffffffffff968716815260208101959095528401929092526060830152909116608082015260a0016100c2565b6101b77f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100c2565b6101b77f000000000000000000000000000000000000000000000000000000000000000081565b6101596102a6565b60008060008060006040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161029d9060208082526027908201527f6e6f7420696d706c656d656e746564202d20757365206c6174657374526f756e60408201527f6444617461282900000000000000000000000000000000000000000000000000606082015260800190565b60405180910390fd5b6000806000806000806102b76102ca565b9096909550429450849350600092509050565b6000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561033357600080fd5b505afa158015610347573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036b9190610691565b50505091505060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b1580156103d957600080fd5b505afa1580156103ed573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041191906106e9565b905061043e82827f0000000000000000000000000000000000000000000000000000000000000000610601565b915060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b1580156104a857600080fd5b505afa1580156104bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e09190610691565b50505091505060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561054e57600080fd5b505afa158015610562573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058691906106e9565b90506105b382827f0000000000000000000000000000000000000000000000000000000000000000610601565b9150816105e460ff7f000000000000000000000000000000000000000000000000000000000000000016600a61086f565b6105ee9086610937565b6105f8919061077f565b94505050505090565b60008160ff168360ff16101561063a5761061b83836109f3565b6106299060ff16600a61086f565b6106339085610937565b905061066d565b8160ff168360ff16111561066a5761065282846109f3565b6106609060ff16600a61086f565b610633908561077f565b50825b9392505050565b60006020828403121561068657600080fd5b813561066d81610a45565b600080600080600060a086880312156106a957600080fd5b85516106b481610a45565b8095505060208601519350604086015192506060860151915060808601516106db81610a45565b809150509295509295909350565b6000602082840312156106fb57600080fd5b815160ff8116811461066d57600080fd5b600060208083528351808285015260005b818110156107395785810183015185820160400152820161071d565b8181111561074b576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000826107b5577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f80000000000000000000000000000000000000000000000000000000000000008314161561080957610809610a16565b500590565b600181815b8085111561086757817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561084d5761084d610a16565b8085161561085a57918102915b93841c9390800290610813565b509250929050565b600061066d838360008261088557506001610931565b8161089257506000610931565b81600181146108a857600281146108b2576108ce565b6001915050610931565b60ff8411156108c3576108c3610a16565b50506001821b610931565b5060208310610133831016604e8410600b84101617156108f1575081810a610931565b6108fb838361080e565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561092d5761092d610a16565b0290505b92915050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60008413600084138583048511828216161561097857610978610a16565b7f800000000000000000000000000000000000000000000000000000000000000060008712868205881281841616156109b3576109b3610a16565b600087129250878205871284841616156109cf576109cf610a16565b878505871281841616156109e5576109e5610a16565b505050929093029392505050565b600060ff821660ff841680821015610a0d57610a0d610a16565b90039392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b69ffffffffffffffffffff81168114610a5d57600080fd5b5056fea164736f6c6343000806000a", } var DerivedPriceFeedABI = DerivedPriceFeedMetaData.ABI @@ -170,9 +168,9 @@ func (_DerivedPriceFeed *DerivedPriceFeedTransactorRaw) Transact(opts *bind.Tran return _DerivedPriceFeed.Contract.contract.Transact(opts, method, params...) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) Base(opts *bind.CallOpts) (common.Address, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) BASE(opts *bind.CallOpts) (common.Address, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "base") + err := _DerivedPriceFeed.contract.Call(opts, &out, "BASE") if err != nil { return *new(common.Address), err @@ -184,17 +182,17 @@ func (_DerivedPriceFeed *DerivedPriceFeedCaller) Base(opts *bind.CallOpts) (comm } -func (_DerivedPriceFeed *DerivedPriceFeedSession) Base() (common.Address, error) { - return _DerivedPriceFeed.Contract.Base(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedSession) BASE() (common.Address, error) { + return _DerivedPriceFeed.Contract.BASE(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Base() (common.Address, error) { - return _DerivedPriceFeed.Contract.Base(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) BASE() (common.Address, error) { + return _DerivedPriceFeed.Contract.BASE(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) Decimals(opts *bind.CallOpts) (uint8, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) DECIMALS(opts *bind.CallOpts) (uint8, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "decimals") + err := _DerivedPriceFeed.contract.Call(opts, &out, "DECIMALS") if err != nil { return *new(uint8), err @@ -206,148 +204,104 @@ func (_DerivedPriceFeed *DerivedPriceFeedCaller) Decimals(opts *bind.CallOpts) ( } -func (_DerivedPriceFeed *DerivedPriceFeedSession) Decimals() (uint8, error) { - return _DerivedPriceFeed.Contract.Decimals(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedSession) DECIMALS() (uint8, error) { + return _DerivedPriceFeed.Contract.DECIMALS(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Decimals() (uint8, error) { - return _DerivedPriceFeed.Contract.Decimals(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) DECIMALS() (uint8, error) { + return _DerivedPriceFeed.Contract.DECIMALS(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) Description(opts *bind.CallOpts) (string, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) QUOTE(opts *bind.CallOpts) (common.Address, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "description") + err := _DerivedPriceFeed.contract.Call(opts, &out, "QUOTE") if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -func (_DerivedPriceFeed *DerivedPriceFeedSession) Description() (string, error) { - return _DerivedPriceFeed.Contract.Description(&_DerivedPriceFeed.CallOpts) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Description() (string, error) { - return _DerivedPriceFeed.Contract.Description(&_DerivedPriceFeed.CallOpts) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCaller) GetAnswer(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) { - var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "getAnswer", arg0) - - if err != nil { - return *new(*big.Int), err + return *new(common.Address), err } - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) return out0, err } -func (_DerivedPriceFeed *DerivedPriceFeedSession) GetAnswer(arg0 *big.Int) (*big.Int, error) { - return _DerivedPriceFeed.Contract.GetAnswer(&_DerivedPriceFeed.CallOpts, arg0) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) GetAnswer(arg0 *big.Int) (*big.Int, error) { - return _DerivedPriceFeed.Contract.GetAnswer(&_DerivedPriceFeed.CallOpts, arg0) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCaller) GetRoundData(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { - var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "getRoundData", arg0) - - if err != nil { - return *new(*big.Int), *new(*big.Int), *new(*big.Int), *new(*big.Int), *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - out1 := *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) - out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) - out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) - out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) - - return out0, out1, out2, out3, out4, err - +func (_DerivedPriceFeed *DerivedPriceFeedSession) QUOTE() (common.Address, error) { + return _DerivedPriceFeed.Contract.QUOTE(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedSession) GetRoundData(arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { - return _DerivedPriceFeed.Contract.GetRoundData(&_DerivedPriceFeed.CallOpts, arg0) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) QUOTE() (common.Address, error) { + return _DerivedPriceFeed.Contract.QUOTE(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) GetRoundData(arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { - return _DerivedPriceFeed.Contract.GetRoundData(&_DerivedPriceFeed.CallOpts, arg0) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCaller) GetTimestamp(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) Decimals(opts *bind.CallOpts) (uint8, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "getTimestamp", arg0) + err := _DerivedPriceFeed.contract.Call(opts, &out, "decimals") if err != nil { - return *new(*big.Int), err + return *new(uint8), err } - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) return out0, err } -func (_DerivedPriceFeed *DerivedPriceFeedSession) GetTimestamp(arg0 *big.Int) (*big.Int, error) { - return _DerivedPriceFeed.Contract.GetTimestamp(&_DerivedPriceFeed.CallOpts, arg0) +func (_DerivedPriceFeed *DerivedPriceFeedSession) Decimals() (uint8, error) { + return _DerivedPriceFeed.Contract.Decimals(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) GetTimestamp(arg0 *big.Int) (*big.Int, error) { - return _DerivedPriceFeed.Contract.GetTimestamp(&_DerivedPriceFeed.CallOpts, arg0) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Decimals() (uint8, error) { + return _DerivedPriceFeed.Contract.Decimals(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) LatestAnswer(opts *bind.CallOpts) (*big.Int, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) Description(opts *bind.CallOpts) (string, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "latestAnswer") + err := _DerivedPriceFeed.contract.Call(opts, &out, "description") if err != nil { - return *new(*big.Int), err + return *new(string), err } - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out0 := *abi.ConvertType(out[0], new(string)).(*string) return out0, err } -func (_DerivedPriceFeed *DerivedPriceFeedSession) LatestAnswer() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestAnswer(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedSession) Description() (string, error) { + return _DerivedPriceFeed.Contract.Description(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) LatestAnswer() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestAnswer(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Description() (string, error) { + return _DerivedPriceFeed.Contract.Description(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) LatestRound(opts *bind.CallOpts) (*big.Int, error) { +func (_DerivedPriceFeed *DerivedPriceFeedCaller) GetRoundData(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "latestRound") + err := _DerivedPriceFeed.contract.Call(opts, &out, "getRoundData", arg0) if err != nil { - return *new(*big.Int), err + return *new(*big.Int), *new(*big.Int), *new(*big.Int), *new(*big.Int), *new(*big.Int), err } out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out1 := *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) - return out0, err + return out0, out1, out2, out3, out4, err } -func (_DerivedPriceFeed *DerivedPriceFeedSession) LatestRound() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestRound(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedSession) GetRoundData(arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { + return _DerivedPriceFeed.Contract.GetRoundData(&_DerivedPriceFeed.CallOpts, arg0) } -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) LatestRound() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestRound(&_DerivedPriceFeed.CallOpts) +func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) GetRoundData(arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) { + return _DerivedPriceFeed.Contract.GetRoundData(&_DerivedPriceFeed.CallOpts, arg0) } func (_DerivedPriceFeed *DerivedPriceFeedCaller) LatestRoundData(opts *bind.CallOpts) (LatestRoundData, @@ -383,50 +337,6 @@ func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) LatestRoundData() (Lates return _DerivedPriceFeed.Contract.LatestRoundData(&_DerivedPriceFeed.CallOpts) } -func (_DerivedPriceFeed *DerivedPriceFeedCaller) LatestTimestamp(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "latestTimestamp") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -func (_DerivedPriceFeed *DerivedPriceFeedSession) LatestTimestamp() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestTimestamp(&_DerivedPriceFeed.CallOpts) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) LatestTimestamp() (*big.Int, error) { - return _DerivedPriceFeed.Contract.LatestTimestamp(&_DerivedPriceFeed.CallOpts) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCaller) Quote(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _DerivedPriceFeed.contract.Call(opts, &out, "quote") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -func (_DerivedPriceFeed *DerivedPriceFeedSession) Quote() (common.Address, error) { - return _DerivedPriceFeed.Contract.Quote(&_DerivedPriceFeed.CallOpts) -} - -func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Quote() (common.Address, error) { - return _DerivedPriceFeed.Contract.Quote(&_DerivedPriceFeed.CallOpts) -} - func (_DerivedPriceFeed *DerivedPriceFeedCaller) Version(opts *bind.CallOpts) (*big.Int, error) { var out []interface{} err := _DerivedPriceFeed.contract.Call(opts, &out, "version") @@ -449,280 +359,6 @@ func (_DerivedPriceFeed *DerivedPriceFeedCallerSession) Version() (*big.Int, err return _DerivedPriceFeed.Contract.Version(&_DerivedPriceFeed.CallOpts) } -type DerivedPriceFeedAnswerUpdatedIterator struct { - Event *DerivedPriceFeedAnswerUpdated - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *DerivedPriceFeedAnswerUpdatedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(DerivedPriceFeedAnswerUpdated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - - select { - case log := <-it.logs: - it.Event = new(DerivedPriceFeedAnswerUpdated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -func (it *DerivedPriceFeedAnswerUpdatedIterator) Error() error { - return it.fail -} - -func (it *DerivedPriceFeedAnswerUpdatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type DerivedPriceFeedAnswerUpdated struct { - Current *big.Int - RoundId *big.Int - UpdatedAt *big.Int - Raw types.Log -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) FilterAnswerUpdated(opts *bind.FilterOpts, current []*big.Int, roundId []*big.Int) (*DerivedPriceFeedAnswerUpdatedIterator, error) { - - var currentRule []interface{} - for _, currentItem := range current { - currentRule = append(currentRule, currentItem) - } - var roundIdRule []interface{} - for _, roundIdItem := range roundId { - roundIdRule = append(roundIdRule, roundIdItem) - } - - logs, sub, err := _DerivedPriceFeed.contract.FilterLogs(opts, "AnswerUpdated", currentRule, roundIdRule) - if err != nil { - return nil, err - } - return &DerivedPriceFeedAnswerUpdatedIterator{contract: _DerivedPriceFeed.contract, event: "AnswerUpdated", logs: logs, sub: sub}, nil -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) WatchAnswerUpdated(opts *bind.WatchOpts, sink chan<- *DerivedPriceFeedAnswerUpdated, current []*big.Int, roundId []*big.Int) (event.Subscription, error) { - - var currentRule []interface{} - for _, currentItem := range current { - currentRule = append(currentRule, currentItem) - } - var roundIdRule []interface{} - for _, roundIdItem := range roundId { - roundIdRule = append(roundIdRule, roundIdItem) - } - - logs, sub, err := _DerivedPriceFeed.contract.WatchLogs(opts, "AnswerUpdated", currentRule, roundIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(DerivedPriceFeedAnswerUpdated) - if err := _DerivedPriceFeed.contract.UnpackLog(event, "AnswerUpdated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) ParseAnswerUpdated(log types.Log) (*DerivedPriceFeedAnswerUpdated, error) { - event := new(DerivedPriceFeedAnswerUpdated) - if err := _DerivedPriceFeed.contract.UnpackLog(event, "AnswerUpdated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type DerivedPriceFeedNewRoundIterator struct { - Event *DerivedPriceFeedNewRound - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *DerivedPriceFeedNewRoundIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(DerivedPriceFeedNewRound) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - - select { - case log := <-it.logs: - it.Event = new(DerivedPriceFeedNewRound) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -func (it *DerivedPriceFeedNewRoundIterator) Error() error { - return it.fail -} - -func (it *DerivedPriceFeedNewRoundIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type DerivedPriceFeedNewRound struct { - RoundId *big.Int - StartedBy common.Address - StartedAt *big.Int - Raw types.Log -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) FilterNewRound(opts *bind.FilterOpts, roundId []*big.Int, startedBy []common.Address) (*DerivedPriceFeedNewRoundIterator, error) { - - var roundIdRule []interface{} - for _, roundIdItem := range roundId { - roundIdRule = append(roundIdRule, roundIdItem) - } - var startedByRule []interface{} - for _, startedByItem := range startedBy { - startedByRule = append(startedByRule, startedByItem) - } - - logs, sub, err := _DerivedPriceFeed.contract.FilterLogs(opts, "NewRound", roundIdRule, startedByRule) - if err != nil { - return nil, err - } - return &DerivedPriceFeedNewRoundIterator{contract: _DerivedPriceFeed.contract, event: "NewRound", logs: logs, sub: sub}, nil -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) WatchNewRound(opts *bind.WatchOpts, sink chan<- *DerivedPriceFeedNewRound, roundId []*big.Int, startedBy []common.Address) (event.Subscription, error) { - - var roundIdRule []interface{} - for _, roundIdItem := range roundId { - roundIdRule = append(roundIdRule, roundIdItem) - } - var startedByRule []interface{} - for _, startedByItem := range startedBy { - startedByRule = append(startedByRule, startedByItem) - } - - logs, sub, err := _DerivedPriceFeed.contract.WatchLogs(opts, "NewRound", roundIdRule, startedByRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(DerivedPriceFeedNewRound) - if err := _DerivedPriceFeed.contract.UnpackLog(event, "NewRound", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -func (_DerivedPriceFeed *DerivedPriceFeedFilterer) ParseNewRound(log types.Log) (*DerivedPriceFeedNewRound, error) { - event := new(DerivedPriceFeedNewRound) - if err := _DerivedPriceFeed.contract.UnpackLog(event, "NewRound", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - type LatestRoundData struct { RoundId *big.Int Answer *big.Int @@ -731,70 +367,28 @@ type LatestRoundData struct { AnsweredInRound *big.Int } -func (_DerivedPriceFeed *DerivedPriceFeed) ParseLog(log types.Log) (generated.AbigenLog, error) { - switch log.Topics[0] { - case _DerivedPriceFeed.abi.Events["AnswerUpdated"].ID: - return _DerivedPriceFeed.ParseAnswerUpdated(log) - case _DerivedPriceFeed.abi.Events["NewRound"].ID: - return _DerivedPriceFeed.ParseNewRound(log) - - default: - return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) - } -} - -func (DerivedPriceFeedAnswerUpdated) Topic() common.Hash { - return common.HexToHash("0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f") -} - -func (DerivedPriceFeedNewRound) Topic() common.Hash { - return common.HexToHash("0x0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271") -} - func (_DerivedPriceFeed *DerivedPriceFeed) Address() common.Address { return _DerivedPriceFeed.address } type DerivedPriceFeedInterface interface { - Base(opts *bind.CallOpts) (common.Address, error) + BASE(opts *bind.CallOpts) (common.Address, error) + + DECIMALS(opts *bind.CallOpts) (uint8, error) + + QUOTE(opts *bind.CallOpts) (common.Address, error) Decimals(opts *bind.CallOpts) (uint8, error) Description(opts *bind.CallOpts) (string, error) - GetAnswer(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) - GetRoundData(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, *big.Int, *big.Int, *big.Int, *big.Int, error) - GetTimestamp(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) - - LatestAnswer(opts *bind.CallOpts) (*big.Int, error) - - LatestRound(opts *bind.CallOpts) (*big.Int, error) - LatestRoundData(opts *bind.CallOpts) (LatestRoundData, error) - LatestTimestamp(opts *bind.CallOpts) (*big.Int, error) - - Quote(opts *bind.CallOpts) (common.Address, error) - Version(opts *bind.CallOpts) (*big.Int, error) - FilterAnswerUpdated(opts *bind.FilterOpts, current []*big.Int, roundId []*big.Int) (*DerivedPriceFeedAnswerUpdatedIterator, error) - - WatchAnswerUpdated(opts *bind.WatchOpts, sink chan<- *DerivedPriceFeedAnswerUpdated, current []*big.Int, roundId []*big.Int) (event.Subscription, error) - - ParseAnswerUpdated(log types.Log) (*DerivedPriceFeedAnswerUpdated, error) - - FilterNewRound(opts *bind.FilterOpts, roundId []*big.Int, startedBy []common.Address) (*DerivedPriceFeedNewRoundIterator, error) - - WatchNewRound(opts *bind.WatchOpts, sink chan<- *DerivedPriceFeedNewRound, roundId []*big.Int, startedBy []common.Address) (event.Subscription, error) - - ParseNewRound(log types.Log) (*DerivedPriceFeedNewRound, error) - - ParseLog(log types.Log) (generated.AbigenLog, error) - Address() common.Address } diff --git a/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper/keeper_registry_vb_wrapper.go b/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper/keeper_registry_vb_wrapper.go new file mode 100644 index 00000000000..78c187cafba --- /dev/null +++ b/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper/keeper_registry_vb_wrapper.go @@ -0,0 +1,3221 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package keeper_registry_vb_wrapper + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +var KeeperRegistryVBMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"linkEthFeed\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"fastGasFeed\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"paymentPremiumPPB\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"flatFeeMicroLink\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"blockCountPerTurn\",\"type\":\"uint24\"},{\"internalType\":\"uint32\",\"name\":\"checkGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"stalenessSeconds\",\"type\":\"uint24\"},{\"internalType\":\"uint16\",\"name\":\"gasCeilingMultiplier\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"fallbackGasPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"fallbackLinkPrice\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"mustTakeTurns\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"paymentPremiumPPB\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint24\",\"name\":\"blockCountPerTurn\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"checkGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint24\",\"name\":\"stalenessSeconds\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"gasCeilingMultiplier\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fallbackGasPrice\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fallbackLinkPrice\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"mustTakeTurns\",\"type\":\"bool\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"flatFeeMicroLink\",\"type\":\"uint32\"}],\"name\":\"FlatFeeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"FundsAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"FundsWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"keepers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"payees\",\"type\":\"address[]\"}],\"name\":\"KeepersUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"keeper\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"PayeeshipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"keeper\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"PayeeshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"keeper\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"payee\",\"type\":\"address\"}],\"name\":\"PaymentWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"RegistrarChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"atBlockHeight\",\"type\":\"uint64\"}],\"name\":\"UpkeepCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"UpkeepPerformed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"executeGas\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"}],\"name\":\"UpkeepRegistered\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"FAST_GAS_FEED\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK\",\"outputs\":[{\"internalType\":\"contractLinkTokenInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK_ETH_FEED\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"keeper\",\"type\":\"address\"}],\"name\":\"acceptPayeeship\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"addFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"cancelUpkeep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"}],\"name\":\"checkUpkeep\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxLinkPayment\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"adjustedGasWei\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"linkEth\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCanceledUpkeepList\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"paymentPremiumPPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"blockCountPerTurn\",\"type\":\"uint24\"},{\"internalType\":\"uint32\",\"name\":\"checkGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"stalenessSeconds\",\"type\":\"uint24\"},{\"internalType\":\"uint16\",\"name\":\"gasCeilingMultiplier\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"fallbackGasPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"fallbackLinkPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFlatFee\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"query\",\"type\":\"address\"}],\"name\":\"getKeeperInfo\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"payee\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint96\",\"name\":\"balance\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getKeeperList\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"getMaxPaymentForGas\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"maxPayment\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getMinBalanceForUpkeep\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"minBalance\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMustTakeTurns\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRegistrar\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getUpkeep\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"executeGas\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"checkData\",\"type\":\"bytes\"},{\"internalType\":\"uint96\",\"name\":\"balance\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"lastKeeper\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"maxValidBlocknumber\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getUpkeepCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"performUpkeep\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"recoverFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"gasLimit\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"checkData\",\"type\":\"bytes\"}],\"name\":\"registerUpkeep\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"paymentPremiumPPB\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"flatFeeMicroLink\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"blockCountPerTurn\",\"type\":\"uint24\"},{\"internalType\":\"uint32\",\"name\":\"checkGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"stalenessSeconds\",\"type\":\"uint24\"},{\"internalType\":\"uint16\",\"name\":\"gasCeilingMultiplier\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"fallbackGasPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"fallbackLinkPrice\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"mustTakeTurns\",\"type\":\"bool\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"keepers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"payees\",\"type\":\"address[]\"}],\"name\":\"setKeepers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"registrar\",\"type\":\"address\"}],\"name\":\"setRegistrar\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"keeper\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"proposed\",\"type\":\"address\"}],\"name\":\"transferPayeeship\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdrawFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdrawPayment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60e06040523480156200001157600080fd5b50604051620054da380380620054da83398181016040526101808110156200003857600080fd5b508051602082015160408301516060840151608085015160a086015160c087015160e08801516101008901516101208a01516101408b0151610160909b0151999a9899979896979596949593949293919290913380600081620000e2576040805162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f0000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620001155762000115816200016e565b50506001600255506003805460ff191690556001600160601b031960608d811b82166080528c811b821660a0528b901b1660c0526200015c8989898989898989896200021e565b505050505050505050505050620004c3565b6001600160a01b038116331415620001cd576040805162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6200022862000461565b6040518060e001604052808a63ffffffff1681526020018963ffffffff1681526020018862ffffff1681526020018763ffffffff1681526020018662ffffff1681526020018561ffff168152602001821515815250600b60008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160086101000a81548162ffffff021916908362ffffff160217905550606082015181600001600b6101000a81548163ffffffff021916908363ffffffff160217905550608082015181600001600f6101000a81548162ffffff021916908362ffffff16021790555060a08201518160000160126101000a81548161ffff021916908361ffff16021790555060c08201518160000160146101000a81548160ff02191690831515021790555090505082600c8190555081600d819055507f6db8cdacf21c3bbd6135926f497c6fba81fd6969684ecf85f56550d2b1f8e6918988888888888888604051808963ffffffff1681526020018862ffffff1681526020018763ffffffff1681526020018662ffffff1681526020018561ffff16815260200184815260200183815260200182151581526020019850505050505050505060405180910390a16040805163ffffffff8a16815290517f17b46a44a823646eef686b7824df2962de896bc9a012a60b67694c5cbf184d8b9181900360200190a1505050505050505050565b6000546001600160a01b03163314620004c1576040805162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015290519081900360640190fd5b565b60805160601c60a05160601c60c05160601c614fb96200052160003980610d2c5280614218525080611c5852806142eb525080610c215280610fd252806113ce52806114a35280611bb55280611def5280611ebd5250614fb96000f3fe608060405234801561001057600080fd5b50600436106102415760003560e01c8063a4c0ed3611610145578063c41b813a116100bd578063eb5dcd6c1161008c578063f2fde38b11610071578063f2fde38b14610b09578063faab9d3914610b3c578063fecf27c914610b6f57610241565b8063eb5dcd6c14610ac6578063ebbece5b14610b0157610241565b8063c41b813a14610809578063c7c3a19a146108d6578063c8048022146109f5578063da5c674114610a1257610241565b8063b121e14711610114578063b79550be116100f9578063b79550be146106e7578063b7fdb436146106ef578063c3f909d4146107b157610241565b8063b121e14714610697578063b657bc9c146106ca57610241565b8063a4c0ed3614610558578063a6afef52146105ea578063a710b22114610654578063ad1783611461068f57610241565b80635c975abb116101d85780638456cb59116101a75780638da5cb5b1161018c5780638da5cb5b146104e157806393f0c1fc146104e9578063948108f71461052757610241565b80638456cb59146104b85780638a601fc8146104c057610241565b80635c975abb146103e4578063744bfe611461040057806379ba5097146104395780637bbaf1ea1461044157610241565b80632cb6864d116102145780632cb6864d146103c25780633f4ba83a146103ca5780634584a419146103d45780634d3f7334146103dc57610241565b806315a126ea14610246578063181f5a771461029e5780631b6b6d231461031b5780631e12b8a51461034c575b600080fd5b61024e610b77565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561028a578181015183820152602001610272565b505050509050019250505060405180910390f35b6102a6610be6565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102e05781810151838201526020016102c8565b50505050905090810190601f16801561030d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610323610c1f565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61037f6004803603602081101561036257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610c43565b6040805173ffffffffffffffffffffffffffffffffffffffff909416845291151560208401526bffffffffffffffffffffffff1682820152519081900360600190f35b61024e610cc1565b6103d2610d18565b005b610323610d2a565b610323610d4e565b6103ec610d6a565b604080519115158252519081900360200190f35b6103d26004803603604081101561041657600080fd5b508035906020013573ffffffffffffffffffffffffffffffffffffffff16610d73565b6103d2611093565b6103ec6004803603604081101561045757600080fd5b8135919081019060408101602082013564010000000081111561047957600080fd5b82018360208201111561048b57600080fd5b803590602001918460018302840111640100000000831117156104ad57600080fd5b509092509050611195565b6103d26111eb565b6104c86111fb565b6040805163ffffffff9092168252519081900360200190f35b61032361120f565b610506600480360360208110156104ff57600080fd5b503561122b565b604080516bffffffffffffffffffffffff9092168252519081900360200190f35b6103d26004803603604081101561053d57600080fd5b50803590602001356bffffffffffffffffffffffff16611261565b6103d26004803603606081101561056e57600080fd5b73ffffffffffffffffffffffffffffffffffffffff823516916020810135918101906060810160408201356401000000008111156105ab57600080fd5b8201836020820111156105bd57600080fd5b803590602001918460018302840111640100000000831117156105df57600080fd5b50909250905061148b565b6103d2600480360361012081101561060157600080fd5b5063ffffffff8135811691602081013582169162ffffff604083013581169260608101359092169160808101359091169061ffff60a0820135169060c08101359060e08101359061010001351515611727565b6103d26004803603604081101561066a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611968565b610323611c56565b6103d2600480360360208110156106ad57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611c7a565b610506600480360360208110156106e057600080fd5b5035611da7565b6103d2611de3565b6103d26004803603604081101561070557600080fd5b81019060208101813564010000000081111561072057600080fd5b82018360208201111561073257600080fd5b8035906020019184602083028401116401000000008311171561075457600080fd5b91939092909160208101903564010000000081111561077257600080fd5b82018360208201111561078457600080fd5b803590602001918460208302840111640100000000831117156107a657600080fd5b509092509050611f73565b6107b9612490565b6040805163ffffffff988916815262ffffff9788166020820152959097168588015292909416606084015261ffff16608083015260a082019290925260c081019190915290519081900360e00190f35b6108426004803603604081101561081f57600080fd5b508035906020013573ffffffffffffffffffffffffffffffffffffffff1661255f565b6040518080602001868152602001858152602001848152602001838152602001828103825287818151815260200191508051906020019080838360005b8381101561089757818101518382015260200161087f565b50505050905090810190601f1680156108c45780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b6108f3600480360360208110156108ec57600080fd5b5035612bfe565b604051808873ffffffffffffffffffffffffffffffffffffffff1681526020018763ffffffff16815260200180602001866bffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018367ffffffffffffffff168152602001828103825287818151815260200191508051906020019080838360005b838110156109b457818101518382015260200161099c565b50505050905090810190601f1680156109e15780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b6103d260048036036020811015610a0b57600080fd5b5035612da7565b610ab460048036036080811015610a2857600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169263ffffffff60208201351692604082013590921691810190608081016060820135640100000000811115610a7557600080fd5b820183602082011115610a8757600080fd5b80359060200191846001830284011164010000000083111715610aa957600080fd5b509092509050613008565b60408051918252519081900360200190f35b6103d260048036036040811015610adc57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516613454565b6103ec61361e565b6103d260048036036020811015610b1f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661363f565b6103d260048036036020811015610b5257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613653565b610ab4613802565b60606006805480602002602001604051908101604052809291908181526020018280548015610bdc57602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610bb1575b5050505050905090565b6040518060400160405280601481526020017f4b6565706572526567697374727920312e312e3000000000000000000000000081525081565b7f000000000000000000000000000000000000000000000000000000000000000081565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526008602090815260409182902082516060810184528154948516808252740100000000000000000000000000000000000000009095046bffffffffffffffffffffffff1692810183905260019091015460ff16151592018290529192909190565b60606005805480602002602001604051908101604052809291908181526020018280548015610bdc57602002820191906000526020600020905b815481526020019060010190808311610cfb575050505050905090565b610d20613808565b610d2861388e565b565b7f000000000000000000000000000000000000000000000000000000000000000081565b600f5473ffffffffffffffffffffffffffffffffffffffff1690565b60035460ff1690565b8073ffffffffffffffffffffffffffffffffffffffff8116610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f63616e6e6f742073656e6420746f207a65726f20616464726573730000000000604482015290519081900360640190fd5b6000838152600760205260409020600101546c01000000000000000000000000900473ffffffffffffffffffffffffffffffffffffffff163314610e9b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6f6e6c792063616c6c61626c652062792061646d696e00000000000000000000604482015290519081900360640190fd5b6000838152600760205260409020600201544367ffffffffffffffff9091161115610f2757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f75706b656570206d7573742062652063616e63656c6564000000000000000000604482015290519081900360640190fd5b600083815260076020526040902060010180547fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116909155600e546bffffffffffffffffffffffff90911690610f7e908261397c565b600e556040805182815273ffffffffffffffffffffffffffffffffffffffff85166020820152815186927ff3b5906e5672f3e524854103bcafbbdba80dbdfeca2c35e116127b1060a68318928290030190a27f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84836040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15801561106157600080fd5b505af1158015611075573d6000803e3d6000fd5b505050506040513d602081101561108b57600080fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461111957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015290519081900360640190fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60006111e36111de338686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506139f3915050565b613ab4565b949350505050565b6111f3613808565b610d2861411d565b600b54640100000000900463ffffffff1690565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b60008060006112386141e5565b9150915060006112498360006143c4565b905061125685828461440a565b93505050505b919050565b60008281526007602052604090206002015467ffffffffffffffff908116146112eb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f75706b656570206d757374206265206163746976650000000000000000000000604482015290519081900360640190fd5b600082815260076020526040902060010154611315906bffffffffffffffffffffffff16826145db565b600083815260076020526040902060010180547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff928316179055600e5461136b918316614667565b600e55604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048201523060248201526bffffffffffffffffffffffff83166044820152905173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016916323b872dd9160648083019260209291908290030181600087803b15801561141657600080fd5b505af115801561142a573d6000803e3d6000fd5b505050506040513d602081101561144057600080fd5b5050604080516bffffffffffffffffffffffff831681529051339184917fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039181900360200190a35050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461152f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6f6e6c792063616c6c61626c65207468726f756768204c494e4b000000000000604482015290519081900360640190fd5b6020811461159e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64617461206d7573742062652033322062797465730000000000000000000000604482015290519081900360640190fd5b6000828260208110156115b057600080fd5b503560008181526007602052604090206002015490915067ffffffffffffffff9081161461163f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f75706b656570206d757374206265206163746976650000000000000000000000604482015290519081900360640190fd5b600081815260076020526040902060010154611669906bffffffffffffffffffffffff16856145db565b600082815260076020526040902060010180547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff92909216919091179055600e546116c29085614667565b600e55604080516bffffffffffffffffffffffff86168152905173ffffffffffffffffffffffffffffffffffffffff87169183917fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039181900360200190a35050505050565b61172f613808565b6040518060e001604052808a63ffffffff1681526020018963ffffffff1681526020018862ffffff1681526020018763ffffffff1681526020018662ffffff1681526020018561ffff168152602001821515815250600b60008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160086101000a81548162ffffff021916908362ffffff160217905550606082015181600001600b6101000a81548163ffffffff021916908363ffffffff160217905550608082015181600001600f6101000a81548162ffffff021916908362ffffff16021790555060a08201518160000160126101000a81548161ffff021916908361ffff16021790555060c08201518160000160146101000a81548160ff02191690831515021790555090505082600c8190555081600d819055507f6db8cdacf21c3bbd6135926f497c6fba81fd6969684ecf85f56550d2b1f8e6918988888888888888604051808963ffffffff1681526020018862ffffff1681526020018763ffffffff1681526020018662ffffff1681526020018561ffff16815260200184815260200183815260200182151581526020019850505050505050505060405180910390a16040805163ffffffff8a16815290517f17b46a44a823646eef686b7824df2962de896bc9a012a60b67694c5cbf184d8b9181900360200190a1505050505050505050565b8073ffffffffffffffffffffffffffffffffffffffff81166119eb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f63616e6e6f742073656e6420746f207a65726f20616464726573730000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff83811660009081526008602090815260409182902082516060810184528154948516808252740100000000000000000000000000000000000000009095046bffffffffffffffffffffffff16928101929092526001015460ff16151591810191909152903314611ad157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6f6e6c792063616c6c61626c6520627920706179656500000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff80851660009081526008602090815260409091208054909216909155810151600e54611b1f916bffffffffffffffffffffffff1661397c565b600e819055508273ffffffffffffffffffffffffffffffffffffffff1681602001516bffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167f9819093176a1851202c7bcfa46845809b4e47c261866550e94ed3775d2f4069833604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a47f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8483602001516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff168152602001826bffffffffffffffffffffffff16815260200192505050602060405180830381600087803b15801561106157600080fd5b7f000000000000000000000000000000000000000000000000000000000000000081565b73ffffffffffffffffffffffffffffffffffffffff818116600090815260096020526040902054163314611d0f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f6f6e6c792063616c6c61626c652062792070726f706f73656420706179656500604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81811660008181526008602090815260408083208054337fffffffffffffffffffffffff000000000000000000000000000000000000000080831682179093556009909452828520805490921690915590519416939092849290917f78af32efdcad432315431e9b03d27e6cd98fb79c405fdc5af7c1714d9c0f75b39190a45050565b600081815260076020526040812054611ddd9074010000000000000000000000000000000000000000900463ffffffff1661122b565b92915050565b611deb613808565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611e7457600080fd5b505afa158015611e88573d6000803e3d6000fd5b505050506040513d6020811015611e9e57600080fd5b5051600e5490915073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb903390611ef190859061397c565b6040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015611f4457600080fd5b505af1158015611f58573d6000803e3d6000fd5b505050506040513d6020811015611f6e57600080fd5b505050565b611f7b613808565b828114611fd3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180614f8c6021913960400191505060405180910390fd5b600283101561204357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6e6f7420656e6f756768206b6565706572730000000000000000000000000000604482015290519081900360640190fd5b60005b6006548110156120c35760006006828154811061205f57fe5b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff1682526008905260409020600190810180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055919091019050612046565b5060005b838110156123ad5760008585838181106120dd57fe5b73ffffffffffffffffffffffffffffffffffffffff602091820293909301358316600081815260089092526040822080549195509316915086868681811061212157fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156121c6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180614f246024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216158061221457508073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b80612234575073ffffffffffffffffffffffffffffffffffffffff818116145b61229f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f63616e6e6f74206368616e676520706179656500000000000000000000000000604482015290519081900360640190fd5b600183015460ff161561231357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f63616e6e6f7420616464206b6565706572207477696365000000000000000000604482015290519081900360640190fd5b600183810180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016909117905573ffffffffffffffffffffffffffffffffffffffff8181161461239d5782547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff82161783555b5050600190920191506120c79050565b506123ba60068585614d99565b507f056264c94f28bb06c99d13f0446eb96c67c215d8d707bce2655a98ddf1c0b71f848484846040518080602001806020018381038352878782818152602001925060200280828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169091018481038352858152602090810191508690860280828437600083820152604051601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018290039850909650505050505050a150505050565b6040805160e081018252600b5463ffffffff80821680845264010000000083048216602085015268010000000000000000830462ffffff9081169585018690526b0100000000000000000000008404909216606085018190526f010000000000000000000000000000008404909216608085018190527201000000000000000000000000000000000000840461ffff1660a086018190527401000000000000000000000000000000000000000090940460ff16151560c090950194909452600c54600d54919692949392909190565b606060008060008061256f610d6a565b156125db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6125e36146db565b6000878152600760209081526040808320815160c081018352815473ffffffffffffffffffffffffffffffffffffffff80821683527401000000000000000000000000000000000000000090910463ffffffff16828601526001808401546bffffffffffffffffffffffff8116848701526c0100000000000000000000000090048216606084015260029384015467ffffffffffffffff8116608085015268010000000000000000900490911660a08301528c8652600a8552838620935160248101958652845461010092811615929092027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190911692909204604483018190529094937f6e04ff0d0000000000000000000000000000000000000000000000000000000093929091829160640190849080156127625780601f1061273757610100808354040283529160200191612762565b820191906000526020600020905b81548152906001019060200180831161274557829003601f168201915b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009097169690961786528751600b549151835193985060009788975073ffffffffffffffffffffffffffffffffffffffff909216955063ffffffff6b01000000000000000000000090930492909216935087928291908083835b6020831061286d57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101612830565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038160008787f1925050503d80600081146128d0576040519150601f19603f3d011682016040523d82523d6000602084013e6128d5565b606091505b509150915081612a705760006128ea82614748565b905060008160405160200180807f63616c6c20746f20636865636b20746172676574206661696c65643a20000000815250601d0182805190602001908083835b6020831061296757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161292a565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018352938490527f08c379a00000000000000000000000000000000000000000000000000000000084526004840181815282516024860152825192975087965094508493604401925085019080838360005b83811015612a35578181015183820152602001612a1d565b50505050905090810190601f168015612a625780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b808060200190516040811015612a8557600080fd5b815160208301805160405192949293830192919084640100000000821115612aac57600080fd5b908301906020820185811115612ac157600080fd5b8251640100000000811182820188101715612adb57600080fd5b82525081516020918201929091019080838360005b83811015612b08578181015183820152602001612af0565b50505050905090810190601f168015612b355780820380516001836020036101000a031916815260200191505b50604052505050809a50819350505081612bb057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f75706b656570206e6f74206e6565646564000000000000000000000000000000604482015290519081900360640190fd5b6000612bbf8b8d8c60006139f3565b9050612bd4858260000151836060015161485f565b6060810151608082015160a083015160c0909301519b9e919d509b50909998509650505050505050565b6000818152600760209081526040808320815160c081018352815473ffffffffffffffffffffffffffffffffffffffff8082168084527401000000000000000000000000000000000000000090920463ffffffff168387018190526001808601546bffffffffffffffffffffffff81168689019081526c010000000000000000000000009091048416606080880191825260029889015467ffffffffffffffff811660808a019081526801000000000000000090910490961660a089019081528d8d52600a8c528a8d20935190519251965184548c5161010097821615979097027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01169a909a04601f81018d90048d0286018d01909b528a85528c9b919a8c9a8b9a8b9a8b9a91999098909796949591939091879190830182828015612d865780601f10612d5b57610100808354040283529160200191612d86565b820191906000526020600020905b815481529060010190602001808311612d6957829003601f168201915b50505050509450975097509750975097509750975050919395979092949650565b60008181526007602052604081206002015467ffffffffffffffff9081169190821490612dd261120f565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161490508180612e1f5750808015612e1f5750438367ffffffffffffffff16115b612e8a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f746f6f206c61746520746f2063616e63656c2075706b65657000000000000000604482015290519081900360640190fd5b8080612ecc57506000848152600760205260409020600101546c01000000000000000000000000900473ffffffffffffffffffffffffffffffffffffffff1633145b612f3757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f6f6e6c79206f776e6572206f722061646d696e00000000000000000000000000604482015290519081900360640190fd5b4381612f4b57612f48816032614667565b90505b600085815260076020526040902060020180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff83161790558215612fca57600580546001810182556000919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0018590555b60405167ffffffffffffffff82169086907f91cb3bb75cfbd718bbfccc56b7f53d92d7048ef4ca39a3b7b7c6d4af1f79118190600090a35050505050565b600061301261120f565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806130625750600f5473ffffffffffffffffffffffffffffffffffffffff1633145b6130b7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614f486023913960400191505060405180910390fd5b6130d68673ffffffffffffffffffffffffffffffffffffffff16614a39565b61314157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f746172676574206973206e6f74206120636f6e74726163740000000000000000604482015290519081900360640190fd5b6108fc8563ffffffff1610156131b857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f6d696e2067617320697320323330300000000000000000000000000000000000604482015290519081900360640190fd5b624c4b408563ffffffff16111561323057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6d61782067617320697320353030303030300000000000000000000000000000604482015290519081900360640190fd5b506004546040805160c08101825273ffffffffffffffffffffffffffffffffffffffff808916825263ffffffff808916602080850191825260008587018181528b86166060880190815267ffffffffffffffff6080890181815260a08a018581528c8652600787528b86209a518b54985190991674010000000000000000000000000000000000000000027fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff998b167fffffffffffffffffffffffff000000000000000000000000000000000000000090991698909817989098169690961789559151600189018054925189166c01000000000000000000000000026bffffffffffffffffffffffff9283167fffffffffffffffffffffffffffffffffffffffff00000000000000000000000090941693909317909116919091179055925160029096018054945190951668010000000000000000027fffffffff0000000000000000000000000000000000000000ffffffffffffffff969093167fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909416939093179490941617909155600a909152206133ec908484614e21565b506004805460010190556040805163ffffffff8716815273ffffffffffffffffffffffffffffffffffffffff86166020820152815183927fbae366358c023f887e791d7a62f2e4316f1026bd77f6fb49501a917b3bc5d012928290030190a295945050505050565b73ffffffffffffffffffffffffffffffffffffffff8281166000908152600860205260409020541633146134e957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6f6e6c792063616c6c61626c6520627920706179656500000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811633141561356e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f63616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff82811660009081526009602052604090205481169082161461361a5773ffffffffffffffffffffffffffffffffffffffff82811660008181526009602052604080822080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169486169485179055513392917f84f7c7c80bb8ed2279b4aab5f61cd05e6374073d38f46d7f32de8c30e9e3836791a45b5050565b600b5474010000000000000000000000000000000000000000900460ff1690565b613647613808565b61365081614a3f565b50565b61365b61120f565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806136ab5750600f5473ffffffffffffffffffffffffffffffffffffffff1633145b613700576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614f486023913960400191505060405180910390fd5b600f5473ffffffffffffffffffffffffffffffffffffffff90811690821681141561378c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f53616d6520726567697374726172000000000000000000000000000000000000604482015290519081900360640190fd5b600f80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84811691821790925560405190918316907f9bf4a5b30267728df68663e14adb47e559863967c419dc6030638883408bed2e90600090a35050565b60045490565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d2857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015290519081900360640190fd5b613896610d6a565b61390157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5061757361626c653a206e6f7420706175736564000000000000000000000000604482015290519081900360640190fd5b600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa613952614b3a565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190a1565b6000828211156139ed57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6139fb614ebb565b60008481526007602052604081205474010000000000000000000000000000000000000000900463ffffffff169080613a326141e5565b915091506000613a4283876143c4565b90506000613a5185838561440a565b6040805160e08101825273ffffffffffffffffffffffffffffffffffffffff8d168152602081018c90529081018a90526bffffffffffffffffffffffff909116606082015260808101959095525060a084015260c0830152509050949350505050565b6000600280541415613b2757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600280556020820151613b3981614b3e565b602083810151600090815260078252604090819020815160c081018352815473ffffffffffffffffffffffffffffffffffffffff80821683527401000000000000000000000000000000000000000090910463ffffffff169482019490945260018201546bffffffffffffffffffffffff8116938201939093526c01000000000000000000000000909204831660608084019190915260029091015467ffffffffffffffff8116608084015268010000000000000000900490921660a08201528451918501519091613c0d9183919061485f565b60005a90506000634585e33b60e01b86604001516040516024018080602001828103825283818151815260200191508051906020019080838360005b83811015613c61578181015183820152602001613c49565b50505050905090810190601f168015613c8e5780820380516001836020036101000a031916815260200191505b50604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009096169590951790945250505060808701518451919250613d239183614bc9565b94505a820391506000613d3f838860a001518960c0015161440a565b6040850151909150613d5f906bffffffffffffffffffffffff1682614c15565b84604001906bffffffffffffffffffffffff1690816bffffffffffffffffffffffff168152505086600001518460a0019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505083600760008960200151815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548163ffffffff021916908363ffffffff16021790555060408201518160010160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550606082015181600101600c6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060808201518160020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555060a08201518160020160086101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050506000613fc082600860008b6000015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166145db90919063ffffffff16565b905080600860008a6000015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550876000015173ffffffffffffffffffffffffffffffffffffffff1687151589602001517fcaacad83e47cc45c280d487ec84184eee2fa3b54ebaa393bda7549f13da228f6858c6040015160405180836bffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b838110156140d25781810151838201526020016140ba565b50505050905090810190601f1680156140ff5780820380516001836020036101000a031916815260200191505b50935050505060405180910390a45050505050506001600255919050565b614125610d6a565b1561419157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258613952614b3a565b6000806000600b600001600f9054906101000a900462ffffff1662ffffff1690506000808263ffffffff161190506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561427c57600080fd5b505afa158015614290573d6000803e3d6000fd5b505050506040513d60a08110156142a657600080fd5b506020810151606090910151925090508280156142ca57508142038463ffffffff16105b806142d6575060008113155b156142e557600c5495506142e9565b8095505b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561434f57600080fd5b505afa158015614363573d6000803e3d6000fd5b505050506040513d60a081101561437957600080fd5b5060208101516060909101519250905082801561439d57508142038463ffffffff16105b806143a9575060008113155b156143b857600d5494506143bc565b8094505b505050509091565b600b546000906143ef9084907201000000000000000000000000000000000000900461ffff16614ca2565b90508180156143fd5750803a105b15611ddd57503a92915050565b6040805160e081018252600b5463ffffffff808216835264010000000082048116602084015262ffffff6801000000000000000083048116948401949094526b0100000000000000000000008204811660608401526f010000000000000000000000000000008204909316608083015261ffff720100000000000000000000000000000000000082041660a083015260ff7401000000000000000000000000000000000000000090910416151560c082015260009182906144dd906144d6908890620138809061466716565b8690614ca2565b90506000614502836000015163ffffffff16633b9aca0061466790919063ffffffff16565b9050600061455361452b64e8d4a51000866020015163ffffffff16614ca290919063ffffffff16565b61454d886145478661454189633b9aca00614ca2565b90614ca2565b90614d15565b90614667565b90506b033b2e3c9fd0803ce80000008111156145d057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f7061796d656e742067726561746572207468616e20616c6c204c494e4b000000604482015290519081900360640190fd5b979650505050505050565b60008282016bffffffffffffffffffffffff808516908216101561466057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b60008282018381101561466057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b3215610d2857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6f6e6c7920666f722073696d756c61746564206261636b656e64000000000000604482015290519081900360640190fd5b606060448251101561478e575060408051808201909152601d81527f7472616e73616374696f6e2072657665727465642073696c656e746c79000000602082015261125c565b60048201805190926024019060208110156147a857600080fd5b81019080805160405193929190846401000000008211156147c857600080fd5b9083019060208201858111156147dd57600080fd5b82516401000000008111828201881017156147f757600080fd5b82525081516020918201929091019080838360005b8381101561482457818101518382015260200161480c565b50505050905090810190601f1680156148515780820380516001836020036101000a031916815260200191505b506040525050509050919050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526008602052604090206001015460ff166148f657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f6f6e6c7920616374697665206b65657065727300000000000000000000000000604482015290519081900360640190fd5b8083604001516bffffffffffffffffffffffff16101561497757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f696e73756666696369656e742066756e64730000000000000000000000000000604482015290519081900360640190fd5b600b5474010000000000000000000000000000000000000000900460ff1615611f6e578173ffffffffffffffffffffffffffffffffffffffff168360a0015173ffffffffffffffffffffffffffffffffffffffff161415611f6e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6b656570657273206d7573742074616b65207475726e73000000000000000000604482015290519081900360640190fd5b3b151590565b73ffffffffffffffffffffffffffffffffffffffff8116331415614ac457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015290519081900360640190fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b3390565b6000818152600760205260409020600201544367ffffffffffffffff9091161161365057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f696e76616c69642075706b656570206964000000000000000000000000000000604482015290519081900360640190fd5b60005a611388811015614bdb57600080fd5b611388810390508460408204820311614bf357600080fd5b50823b614bff57600080fd5b60008083516020850160008789f1949350505050565b6000826bffffffffffffffffffffffff16826bffffffffffffffffffffffff1611156139ed57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b600082614cb157506000611ddd565b82820282848281614cbe57fe5b0414614660576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180614f6b6021913960400191505060405180910390fd5b6000808211614d8557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b6000828481614d9057fe5b04949350505050565b828054828255906000526020600020908101928215614e11579160200282015b82811115614e115781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff843516178255602090920191600190910190614db9565b50614e1d929150614f0e565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282614e575760008555614e11565b82601f10614e8e578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00823516178555614e11565b82800160010185558215614e11579182015b82811115614e11578235825591602001919060010190614ea0565b6040518060e00160405280600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160608152602001600081526020016000815260200160008152602001600081525090565b5b80821115614e1d5760008155600101614f0f56fe63616e6e6f742073657420706179656520746f20746865207a65726f20616464726573734f6e6c792063616c6c61626c65206279206f776e6572206f7220726567697374726172536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7761646472657373206c69737473206e6f74207468652073616d65206c656e677468a164736f6c6343000706000a", +} + +var KeeperRegistryVBABI = KeeperRegistryVBMetaData.ABI + +var KeeperRegistryVBBin = KeeperRegistryVBMetaData.Bin + +func DeployKeeperRegistryVB(auth *bind.TransactOpts, backend bind.ContractBackend, link common.Address, linkEthFeed common.Address, fastGasFeed common.Address, paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int, mustTakeTurns bool) (common.Address, *types.Transaction, *KeeperRegistryVB, error) { + parsed, err := KeeperRegistryVBMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(KeeperRegistryVBBin), backend, link, linkEthFeed, fastGasFeed, paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, fallbackGasPrice, fallbackLinkPrice, mustTakeTurns) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &KeeperRegistryVB{KeeperRegistryVBCaller: KeeperRegistryVBCaller{contract: contract}, KeeperRegistryVBTransactor: KeeperRegistryVBTransactor{contract: contract}, KeeperRegistryVBFilterer: KeeperRegistryVBFilterer{contract: contract}}, nil +} + +type KeeperRegistryVB struct { + address common.Address + abi abi.ABI + KeeperRegistryVBCaller + KeeperRegistryVBTransactor + KeeperRegistryVBFilterer +} + +type KeeperRegistryVBCaller struct { + contract *bind.BoundContract +} + +type KeeperRegistryVBTransactor struct { + contract *bind.BoundContract +} + +type KeeperRegistryVBFilterer struct { + contract *bind.BoundContract +} + +type KeeperRegistryVBSession struct { + Contract *KeeperRegistryVB + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type KeeperRegistryVBCallerSession struct { + Contract *KeeperRegistryVBCaller + CallOpts bind.CallOpts +} + +type KeeperRegistryVBTransactorSession struct { + Contract *KeeperRegistryVBTransactor + TransactOpts bind.TransactOpts +} + +type KeeperRegistryVBRaw struct { + Contract *KeeperRegistryVB +} + +type KeeperRegistryVBCallerRaw struct { + Contract *KeeperRegistryVBCaller +} + +type KeeperRegistryVBTransactorRaw struct { + Contract *KeeperRegistryVBTransactor +} + +func NewKeeperRegistryVB(address common.Address, backend bind.ContractBackend) (*KeeperRegistryVB, error) { + abi, err := abi.JSON(strings.NewReader(KeeperRegistryVBABI)) + if err != nil { + return nil, err + } + contract, err := bindKeeperRegistryVB(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &KeeperRegistryVB{address: address, abi: abi, KeeperRegistryVBCaller: KeeperRegistryVBCaller{contract: contract}, KeeperRegistryVBTransactor: KeeperRegistryVBTransactor{contract: contract}, KeeperRegistryVBFilterer: KeeperRegistryVBFilterer{contract: contract}}, nil +} + +func NewKeeperRegistryVBCaller(address common.Address, caller bind.ContractCaller) (*KeeperRegistryVBCaller, error) { + contract, err := bindKeeperRegistryVB(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &KeeperRegistryVBCaller{contract: contract}, nil +} + +func NewKeeperRegistryVBTransactor(address common.Address, transactor bind.ContractTransactor) (*KeeperRegistryVBTransactor, error) { + contract, err := bindKeeperRegistryVB(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &KeeperRegistryVBTransactor{contract: contract}, nil +} + +func NewKeeperRegistryVBFilterer(address common.Address, filterer bind.ContractFilterer) (*KeeperRegistryVBFilterer, error) { + contract, err := bindKeeperRegistryVB(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &KeeperRegistryVBFilterer{contract: contract}, nil +} + +func bindKeeperRegistryVB(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(KeeperRegistryVBABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _KeeperRegistryVB.Contract.KeeperRegistryVBCaller.contract.Call(opts, result, method, params...) +} + +func (_KeeperRegistryVB *KeeperRegistryVBRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.KeeperRegistryVBTransactor.contract.Transfer(opts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.KeeperRegistryVBTransactor.contract.Transact(opts, method, params...) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _KeeperRegistryVB.Contract.contract.Call(opts, result, method, params...) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.contract.Transfer(opts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.contract.Transact(opts, method, params...) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) FASTGASFEED(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "FAST_GAS_FEED") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) FASTGASFEED() (common.Address, error) { + return _KeeperRegistryVB.Contract.FASTGASFEED(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) FASTGASFEED() (common.Address, error) { + return _KeeperRegistryVB.Contract.FASTGASFEED(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) LINK(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "LINK") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) LINK() (common.Address, error) { + return _KeeperRegistryVB.Contract.LINK(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) LINK() (common.Address, error) { + return _KeeperRegistryVB.Contract.LINK(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) LINKETHFEED(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "LINK_ETH_FEED") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) LINKETHFEED() (common.Address, error) { + return _KeeperRegistryVB.Contract.LINKETHFEED(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) LINKETHFEED() (common.Address, error) { + return _KeeperRegistryVB.Contract.LINKETHFEED(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetCanceledUpkeepList(opts *bind.CallOpts) ([]*big.Int, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getCanceledUpkeepList") + + if err != nil { + return *new([]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetCanceledUpkeepList() ([]*big.Int, error) { + return _KeeperRegistryVB.Contract.GetCanceledUpkeepList(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetCanceledUpkeepList() ([]*big.Int, error) { + return _KeeperRegistryVB.Contract.GetCanceledUpkeepList(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetConfig(opts *bind.CallOpts) (GetConfig, + + error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getConfig") + + outstruct := new(GetConfig) + if err != nil { + return *outstruct, err + } + + outstruct.PaymentPremiumPPB = *abi.ConvertType(out[0], new(uint32)).(*uint32) + outstruct.BlockCountPerTurn = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.CheckGasLimit = *abi.ConvertType(out[2], new(uint32)).(*uint32) + outstruct.StalenessSeconds = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.GasCeilingMultiplier = *abi.ConvertType(out[4], new(uint16)).(*uint16) + outstruct.FallbackGasPrice = *abi.ConvertType(out[5], new(*big.Int)).(**big.Int) + outstruct.FallbackLinkPrice = *abi.ConvertType(out[6], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetConfig() (GetConfig, + + error) { + return _KeeperRegistryVB.Contract.GetConfig(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetConfig() (GetConfig, + + error) { + return _KeeperRegistryVB.Contract.GetConfig(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetFlatFee(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getFlatFee") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetFlatFee() (uint32, error) { + return _KeeperRegistryVB.Contract.GetFlatFee(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetFlatFee() (uint32, error) { + return _KeeperRegistryVB.Contract.GetFlatFee(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetKeeperInfo(opts *bind.CallOpts, query common.Address) (GetKeeperInfo, + + error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getKeeperInfo", query) + + outstruct := new(GetKeeperInfo) + if err != nil { + return *outstruct, err + } + + outstruct.Payee = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + outstruct.Active = *abi.ConvertType(out[1], new(bool)).(*bool) + outstruct.Balance = *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetKeeperInfo(query common.Address) (GetKeeperInfo, + + error) { + return _KeeperRegistryVB.Contract.GetKeeperInfo(&_KeeperRegistryVB.CallOpts, query) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetKeeperInfo(query common.Address) (GetKeeperInfo, + + error) { + return _KeeperRegistryVB.Contract.GetKeeperInfo(&_KeeperRegistryVB.CallOpts, query) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetKeeperList(opts *bind.CallOpts) ([]common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getKeeperList") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetKeeperList() ([]common.Address, error) { + return _KeeperRegistryVB.Contract.GetKeeperList(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetKeeperList() ([]common.Address, error) { + return _KeeperRegistryVB.Contract.GetKeeperList(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetMaxPaymentForGas(opts *bind.CallOpts, gasLimit *big.Int) (*big.Int, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getMaxPaymentForGas", gasLimit) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetMaxPaymentForGas(gasLimit *big.Int) (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetMaxPaymentForGas(&_KeeperRegistryVB.CallOpts, gasLimit) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetMaxPaymentForGas(gasLimit *big.Int) (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetMaxPaymentForGas(&_KeeperRegistryVB.CallOpts, gasLimit) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetMinBalanceForUpkeep(opts *bind.CallOpts, id *big.Int) (*big.Int, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getMinBalanceForUpkeep", id) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetMinBalanceForUpkeep(id *big.Int) (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetMinBalanceForUpkeep(&_KeeperRegistryVB.CallOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetMinBalanceForUpkeep(id *big.Int) (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetMinBalanceForUpkeep(&_KeeperRegistryVB.CallOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetMustTakeTurns(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getMustTakeTurns") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetMustTakeTurns() (bool, error) { + return _KeeperRegistryVB.Contract.GetMustTakeTurns(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetMustTakeTurns() (bool, error) { + return _KeeperRegistryVB.Contract.GetMustTakeTurns(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetRegistrar(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getRegistrar") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetRegistrar() (common.Address, error) { + return _KeeperRegistryVB.Contract.GetRegistrar(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetRegistrar() (common.Address, error) { + return _KeeperRegistryVB.Contract.GetRegistrar(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetUpkeep(opts *bind.CallOpts, id *big.Int) (GetUpkeep, + + error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getUpkeep", id) + + outstruct := new(GetUpkeep) + if err != nil { + return *outstruct, err + } + + outstruct.Target = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + outstruct.ExecuteGas = *abi.ConvertType(out[1], new(uint32)).(*uint32) + outstruct.CheckData = *abi.ConvertType(out[2], new([]byte)).(*[]byte) + outstruct.Balance = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.LastKeeper = *abi.ConvertType(out[4], new(common.Address)).(*common.Address) + outstruct.Admin = *abi.ConvertType(out[5], new(common.Address)).(*common.Address) + outstruct.MaxValidBlocknumber = *abi.ConvertType(out[6], new(uint64)).(*uint64) + + return *outstruct, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetUpkeep(id *big.Int) (GetUpkeep, + + error) { + return _KeeperRegistryVB.Contract.GetUpkeep(&_KeeperRegistryVB.CallOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetUpkeep(id *big.Int) (GetUpkeep, + + error) { + return _KeeperRegistryVB.Contract.GetUpkeep(&_KeeperRegistryVB.CallOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) GetUpkeepCount(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "getUpkeepCount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) GetUpkeepCount() (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetUpkeepCount(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) GetUpkeepCount() (*big.Int, error) { + return _KeeperRegistryVB.Contract.GetUpkeepCount(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) Owner() (common.Address, error) { + return _KeeperRegistryVB.Contract.Owner(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) Owner() (common.Address, error) { + return _KeeperRegistryVB.Contract.Owner(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) Paused(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "paused") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) Paused() (bool, error) { + return _KeeperRegistryVB.Contract.Paused(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) Paused() (bool, error) { + return _KeeperRegistryVB.Contract.Paused(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _KeeperRegistryVB.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) TypeAndVersion() (string, error) { + return _KeeperRegistryVB.Contract.TypeAndVersion(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBCallerSession) TypeAndVersion() (string, error) { + return _KeeperRegistryVB.Contract.TypeAndVersion(&_KeeperRegistryVB.CallOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "acceptOwnership") +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) AcceptOwnership() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AcceptOwnership(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AcceptOwnership(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) AcceptPayeeship(opts *bind.TransactOpts, keeper common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "acceptPayeeship", keeper) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) AcceptPayeeship(keeper common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AcceptPayeeship(&_KeeperRegistryVB.TransactOpts, keeper) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) AcceptPayeeship(keeper common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AcceptPayeeship(&_KeeperRegistryVB.TransactOpts, keeper) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) AddFunds(opts *bind.TransactOpts, id *big.Int, amount *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "addFunds", id, amount) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) AddFunds(id *big.Int, amount *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AddFunds(&_KeeperRegistryVB.TransactOpts, id, amount) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) AddFunds(id *big.Int, amount *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.AddFunds(&_KeeperRegistryVB.TransactOpts, id, amount) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) CancelUpkeep(opts *bind.TransactOpts, id *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "cancelUpkeep", id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) CancelUpkeep(id *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.CancelUpkeep(&_KeeperRegistryVB.TransactOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) CancelUpkeep(id *big.Int) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.CancelUpkeep(&_KeeperRegistryVB.TransactOpts, id) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) CheckUpkeep(opts *bind.TransactOpts, id *big.Int, from common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "checkUpkeep", id, from) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) CheckUpkeep(id *big.Int, from common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.CheckUpkeep(&_KeeperRegistryVB.TransactOpts, id, from) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) CheckUpkeep(id *big.Int, from common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.CheckUpkeep(&_KeeperRegistryVB.TransactOpts, id, from) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) OnTokenTransfer(opts *bind.TransactOpts, sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "onTokenTransfer", sender, amount, data) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) OnTokenTransfer(sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.OnTokenTransfer(&_KeeperRegistryVB.TransactOpts, sender, amount, data) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) OnTokenTransfer(sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.OnTokenTransfer(&_KeeperRegistryVB.TransactOpts, sender, amount, data) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) Pause(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "pause") +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) Pause() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.Pause(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) Pause() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.Pause(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) PerformUpkeep(opts *bind.TransactOpts, id *big.Int, performData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "performUpkeep", id, performData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) PerformUpkeep(id *big.Int, performData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.PerformUpkeep(&_KeeperRegistryVB.TransactOpts, id, performData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) PerformUpkeep(id *big.Int, performData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.PerformUpkeep(&_KeeperRegistryVB.TransactOpts, id, performData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) RecoverFunds(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "recoverFunds") +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) RecoverFunds() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.RecoverFunds(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) RecoverFunds() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.RecoverFunds(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) RegisterUpkeep(opts *bind.TransactOpts, target common.Address, gasLimit uint32, admin common.Address, checkData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "registerUpkeep", target, gasLimit, admin, checkData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) RegisterUpkeep(target common.Address, gasLimit uint32, admin common.Address, checkData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.RegisterUpkeep(&_KeeperRegistryVB.TransactOpts, target, gasLimit, admin, checkData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) RegisterUpkeep(target common.Address, gasLimit uint32, admin common.Address, checkData []byte) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.RegisterUpkeep(&_KeeperRegistryVB.TransactOpts, target, gasLimit, admin, checkData) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) SetConfig(opts *bind.TransactOpts, paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int, mustTakeTurns bool) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "setConfig", paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, fallbackGasPrice, fallbackLinkPrice, mustTakeTurns) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) SetConfig(paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int, mustTakeTurns bool) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetConfig(&_KeeperRegistryVB.TransactOpts, paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, fallbackGasPrice, fallbackLinkPrice, mustTakeTurns) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) SetConfig(paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int, mustTakeTurns bool) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetConfig(&_KeeperRegistryVB.TransactOpts, paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, fallbackGasPrice, fallbackLinkPrice, mustTakeTurns) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) SetKeepers(opts *bind.TransactOpts, keepers []common.Address, payees []common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "setKeepers", keepers, payees) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) SetKeepers(keepers []common.Address, payees []common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetKeepers(&_KeeperRegistryVB.TransactOpts, keepers, payees) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) SetKeepers(keepers []common.Address, payees []common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetKeepers(&_KeeperRegistryVB.TransactOpts, keepers, payees) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) SetRegistrar(opts *bind.TransactOpts, registrar common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "setRegistrar", registrar) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) SetRegistrar(registrar common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetRegistrar(&_KeeperRegistryVB.TransactOpts, registrar) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) SetRegistrar(registrar common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.SetRegistrar(&_KeeperRegistryVB.TransactOpts, registrar) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "transferOwnership", to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.TransferOwnership(&_KeeperRegistryVB.TransactOpts, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.TransferOwnership(&_KeeperRegistryVB.TransactOpts, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) TransferPayeeship(opts *bind.TransactOpts, keeper common.Address, proposed common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "transferPayeeship", keeper, proposed) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) TransferPayeeship(keeper common.Address, proposed common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.TransferPayeeship(&_KeeperRegistryVB.TransactOpts, keeper, proposed) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) TransferPayeeship(keeper common.Address, proposed common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.TransferPayeeship(&_KeeperRegistryVB.TransactOpts, keeper, proposed) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) Unpause(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "unpause") +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) Unpause() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.Unpause(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) Unpause() (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.Unpause(&_KeeperRegistryVB.TransactOpts) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) WithdrawFunds(opts *bind.TransactOpts, id *big.Int, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "withdrawFunds", id, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) WithdrawFunds(id *big.Int, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.WithdrawFunds(&_KeeperRegistryVB.TransactOpts, id, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) WithdrawFunds(id *big.Int, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.WithdrawFunds(&_KeeperRegistryVB.TransactOpts, id, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactor) WithdrawPayment(opts *bind.TransactOpts, from common.Address, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.contract.Transact(opts, "withdrawPayment", from, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBSession) WithdrawPayment(from common.Address, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.WithdrawPayment(&_KeeperRegistryVB.TransactOpts, from, to) +} + +func (_KeeperRegistryVB *KeeperRegistryVBTransactorSession) WithdrawPayment(from common.Address, to common.Address) (*types.Transaction, error) { + return _KeeperRegistryVB.Contract.WithdrawPayment(&_KeeperRegistryVB.TransactOpts, from, to) +} + +type KeeperRegistryVBConfigSetIterator struct { + Event *KeeperRegistryVBConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBConfigSetIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBConfigSet struct { + PaymentPremiumPPB uint32 + BlockCountPerTurn *big.Int + CheckGasLimit uint32 + StalenessSeconds *big.Int + GasCeilingMultiplier uint16 + FallbackGasPrice *big.Int + FallbackLinkPrice *big.Int + MustTakeTurns bool + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterConfigSet(opts *bind.FilterOpts) (*KeeperRegistryVBConfigSetIterator, error) { + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return &KeeperRegistryVBConfigSetIterator{contract: _KeeperRegistryVB.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBConfigSet) (event.Subscription, error) { + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBConfigSet) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseConfigSet(log types.Log) (*KeeperRegistryVBConfigSet, error) { + event := new(KeeperRegistryVBConfigSet) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBFlatFeeSetIterator struct { + Event *KeeperRegistryVBFlatFeeSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBFlatFeeSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFlatFeeSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFlatFeeSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBFlatFeeSetIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBFlatFeeSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBFlatFeeSet struct { + FlatFeeMicroLink uint32 + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterFlatFeeSet(opts *bind.FilterOpts) (*KeeperRegistryVBFlatFeeSetIterator, error) { + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "FlatFeeSet") + if err != nil { + return nil, err + } + return &KeeperRegistryVBFlatFeeSetIterator{contract: _KeeperRegistryVB.contract, event: "FlatFeeSet", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchFlatFeeSet(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFlatFeeSet) (event.Subscription, error) { + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "FlatFeeSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBFlatFeeSet) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FlatFeeSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseFlatFeeSet(log types.Log) (*KeeperRegistryVBFlatFeeSet, error) { + event := new(KeeperRegistryVBFlatFeeSet) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FlatFeeSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBFundsAddedIterator struct { + Event *KeeperRegistryVBFundsAdded + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBFundsAddedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFundsAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFundsAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBFundsAddedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBFundsAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBFundsAdded struct { + Id *big.Int + From common.Address + Amount *big.Int + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterFundsAdded(opts *bind.FilterOpts, id []*big.Int, from []common.Address) (*KeeperRegistryVBFundsAddedIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "FundsAdded", idRule, fromRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBFundsAddedIterator{contract: _KeeperRegistryVB.contract, event: "FundsAdded", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchFundsAdded(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFundsAdded, id []*big.Int, from []common.Address) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "FundsAdded", idRule, fromRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBFundsAdded) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FundsAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseFundsAdded(log types.Log) (*KeeperRegistryVBFundsAdded, error) { + event := new(KeeperRegistryVBFundsAdded) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FundsAdded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBFundsWithdrawnIterator struct { + Event *KeeperRegistryVBFundsWithdrawn + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBFundsWithdrawnIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFundsWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBFundsWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBFundsWithdrawnIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBFundsWithdrawnIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBFundsWithdrawn struct { + Id *big.Int + Amount *big.Int + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterFundsWithdrawn(opts *bind.FilterOpts, id []*big.Int) (*KeeperRegistryVBFundsWithdrawnIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "FundsWithdrawn", idRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBFundsWithdrawnIterator{contract: _KeeperRegistryVB.contract, event: "FundsWithdrawn", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchFundsWithdrawn(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFundsWithdrawn, id []*big.Int) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "FundsWithdrawn", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBFundsWithdrawn) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FundsWithdrawn", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseFundsWithdrawn(log types.Log) (*KeeperRegistryVBFundsWithdrawn, error) { + event := new(KeeperRegistryVBFundsWithdrawn) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "FundsWithdrawn", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBKeepersUpdatedIterator struct { + Event *KeeperRegistryVBKeepersUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBKeepersUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBKeepersUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBKeepersUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBKeepersUpdatedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBKeepersUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBKeepersUpdated struct { + Keepers []common.Address + Payees []common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterKeepersUpdated(opts *bind.FilterOpts) (*KeeperRegistryVBKeepersUpdatedIterator, error) { + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "KeepersUpdated") + if err != nil { + return nil, err + } + return &KeeperRegistryVBKeepersUpdatedIterator{contract: _KeeperRegistryVB.contract, event: "KeepersUpdated", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchKeepersUpdated(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBKeepersUpdated) (event.Subscription, error) { + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "KeepersUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBKeepersUpdated) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "KeepersUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseKeepersUpdated(log types.Log) (*KeeperRegistryVBKeepersUpdated, error) { + event := new(KeeperRegistryVBKeepersUpdated) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "KeepersUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBOwnershipTransferRequestedIterator struct { + Event *KeeperRegistryVBOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBOwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBOwnershipTransferRequestedIterator{contract: _KeeperRegistryVB.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBOwnershipTransferRequested) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseOwnershipTransferRequested(log types.Log) (*KeeperRegistryVBOwnershipTransferRequested, error) { + event := new(KeeperRegistryVBOwnershipTransferRequested) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBOwnershipTransferredIterator struct { + Event *KeeperRegistryVBOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBOwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBOwnershipTransferredIterator{contract: _KeeperRegistryVB.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBOwnershipTransferred) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseOwnershipTransferred(log types.Log) (*KeeperRegistryVBOwnershipTransferred, error) { + event := new(KeeperRegistryVBOwnershipTransferred) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBPausedIterator struct { + Event *KeeperRegistryVBPaused + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBPausedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBPausedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBPausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBPaused struct { + Account common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterPaused(opts *bind.FilterOpts) (*KeeperRegistryVBPausedIterator, error) { + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "Paused") + if err != nil { + return nil, err + } + return &KeeperRegistryVBPausedIterator{contract: _KeeperRegistryVB.contract, event: "Paused", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchPaused(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPaused) (event.Subscription, error) { + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "Paused") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBPaused) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "Paused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParsePaused(log types.Log) (*KeeperRegistryVBPaused, error) { + event := new(KeeperRegistryVBPaused) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "Paused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBPayeeshipTransferRequestedIterator struct { + Event *KeeperRegistryVBPayeeshipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBPayeeshipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPayeeshipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPayeeshipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBPayeeshipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBPayeeshipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBPayeeshipTransferRequested struct { + Keeper common.Address + From common.Address + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterPayeeshipTransferRequested(opts *bind.FilterOpts, keeper []common.Address, from []common.Address, to []common.Address) (*KeeperRegistryVBPayeeshipTransferRequestedIterator, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "PayeeshipTransferRequested", keeperRule, fromRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBPayeeshipTransferRequestedIterator{contract: _KeeperRegistryVB.contract, event: "PayeeshipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchPayeeshipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPayeeshipTransferRequested, keeper []common.Address, from []common.Address, to []common.Address) (event.Subscription, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "PayeeshipTransferRequested", keeperRule, fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBPayeeshipTransferRequested) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PayeeshipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParsePayeeshipTransferRequested(log types.Log) (*KeeperRegistryVBPayeeshipTransferRequested, error) { + event := new(KeeperRegistryVBPayeeshipTransferRequested) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PayeeshipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBPayeeshipTransferredIterator struct { + Event *KeeperRegistryVBPayeeshipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBPayeeshipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPayeeshipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPayeeshipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBPayeeshipTransferredIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBPayeeshipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBPayeeshipTransferred struct { + Keeper common.Address + From common.Address + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterPayeeshipTransferred(opts *bind.FilterOpts, keeper []common.Address, from []common.Address, to []common.Address) (*KeeperRegistryVBPayeeshipTransferredIterator, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "PayeeshipTransferred", keeperRule, fromRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBPayeeshipTransferredIterator{contract: _KeeperRegistryVB.contract, event: "PayeeshipTransferred", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchPayeeshipTransferred(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPayeeshipTransferred, keeper []common.Address, from []common.Address, to []common.Address) (event.Subscription, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "PayeeshipTransferred", keeperRule, fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBPayeeshipTransferred) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PayeeshipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParsePayeeshipTransferred(log types.Log) (*KeeperRegistryVBPayeeshipTransferred, error) { + event := new(KeeperRegistryVBPayeeshipTransferred) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PayeeshipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBPaymentWithdrawnIterator struct { + Event *KeeperRegistryVBPaymentWithdrawn + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBPaymentWithdrawnIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPaymentWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBPaymentWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBPaymentWithdrawnIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBPaymentWithdrawnIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBPaymentWithdrawn struct { + Keeper common.Address + Amount *big.Int + To common.Address + Payee common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterPaymentWithdrawn(opts *bind.FilterOpts, keeper []common.Address, amount []*big.Int, to []common.Address) (*KeeperRegistryVBPaymentWithdrawnIterator, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var amountRule []interface{} + for _, amountItem := range amount { + amountRule = append(amountRule, amountItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "PaymentWithdrawn", keeperRule, amountRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBPaymentWithdrawnIterator{contract: _KeeperRegistryVB.contract, event: "PaymentWithdrawn", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchPaymentWithdrawn(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPaymentWithdrawn, keeper []common.Address, amount []*big.Int, to []common.Address) (event.Subscription, error) { + + var keeperRule []interface{} + for _, keeperItem := range keeper { + keeperRule = append(keeperRule, keeperItem) + } + var amountRule []interface{} + for _, amountItem := range amount { + amountRule = append(amountRule, amountItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "PaymentWithdrawn", keeperRule, amountRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBPaymentWithdrawn) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PaymentWithdrawn", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParsePaymentWithdrawn(log types.Log) (*KeeperRegistryVBPaymentWithdrawn, error) { + event := new(KeeperRegistryVBPaymentWithdrawn) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "PaymentWithdrawn", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBRegistrarChangedIterator struct { + Event *KeeperRegistryVBRegistrarChanged + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBRegistrarChangedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBRegistrarChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBRegistrarChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBRegistrarChangedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBRegistrarChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBRegistrarChanged struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterRegistrarChanged(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBRegistrarChangedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "RegistrarChanged", fromRule, toRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBRegistrarChangedIterator{contract: _KeeperRegistryVB.contract, event: "RegistrarChanged", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchRegistrarChanged(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBRegistrarChanged, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "RegistrarChanged", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBRegistrarChanged) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "RegistrarChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseRegistrarChanged(log types.Log) (*KeeperRegistryVBRegistrarChanged, error) { + event := new(KeeperRegistryVBRegistrarChanged) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "RegistrarChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBUnpausedIterator struct { + Event *KeeperRegistryVBUnpaused + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBUnpausedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBUnpausedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBUnpausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBUnpaused struct { + Account common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterUnpaused(opts *bind.FilterOpts) (*KeeperRegistryVBUnpausedIterator, error) { + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "Unpaused") + if err != nil { + return nil, err + } + return &KeeperRegistryVBUnpausedIterator{contract: _KeeperRegistryVB.contract, event: "Unpaused", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchUnpaused(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUnpaused) (event.Subscription, error) { + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "Unpaused") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBUnpaused) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "Unpaused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseUnpaused(log types.Log) (*KeeperRegistryVBUnpaused, error) { + event := new(KeeperRegistryVBUnpaused) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "Unpaused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBUpkeepCanceledIterator struct { + Event *KeeperRegistryVBUpkeepCanceled + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBUpkeepCanceledIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepCanceled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepCanceled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBUpkeepCanceledIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBUpkeepCanceledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBUpkeepCanceled struct { + Id *big.Int + AtBlockHeight uint64 + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterUpkeepCanceled(opts *bind.FilterOpts, id []*big.Int, atBlockHeight []uint64) (*KeeperRegistryVBUpkeepCanceledIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var atBlockHeightRule []interface{} + for _, atBlockHeightItem := range atBlockHeight { + atBlockHeightRule = append(atBlockHeightRule, atBlockHeightItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "UpkeepCanceled", idRule, atBlockHeightRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBUpkeepCanceledIterator{contract: _KeeperRegistryVB.contract, event: "UpkeepCanceled", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchUpkeepCanceled(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepCanceled, id []*big.Int, atBlockHeight []uint64) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var atBlockHeightRule []interface{} + for _, atBlockHeightItem := range atBlockHeight { + atBlockHeightRule = append(atBlockHeightRule, atBlockHeightItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "UpkeepCanceled", idRule, atBlockHeightRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBUpkeepCanceled) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepCanceled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseUpkeepCanceled(log types.Log) (*KeeperRegistryVBUpkeepCanceled, error) { + event := new(KeeperRegistryVBUpkeepCanceled) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepCanceled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBUpkeepPerformedIterator struct { + Event *KeeperRegistryVBUpkeepPerformed + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBUpkeepPerformedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepPerformed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepPerformed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBUpkeepPerformedIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBUpkeepPerformedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBUpkeepPerformed struct { + Id *big.Int + Success bool + From common.Address + Payment *big.Int + PerformData []byte + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterUpkeepPerformed(opts *bind.FilterOpts, id []*big.Int, success []bool, from []common.Address) (*KeeperRegistryVBUpkeepPerformedIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var successRule []interface{} + for _, successItem := range success { + successRule = append(successRule, successItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "UpkeepPerformed", idRule, successRule, fromRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBUpkeepPerformedIterator{contract: _KeeperRegistryVB.contract, event: "UpkeepPerformed", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchUpkeepPerformed(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepPerformed, id []*big.Int, success []bool, from []common.Address) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + var successRule []interface{} + for _, successItem := range success { + successRule = append(successRule, successItem) + } + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "UpkeepPerformed", idRule, successRule, fromRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBUpkeepPerformed) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepPerformed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseUpkeepPerformed(log types.Log) (*KeeperRegistryVBUpkeepPerformed, error) { + event := new(KeeperRegistryVBUpkeepPerformed) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepPerformed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeeperRegistryVBUpkeepRegisteredIterator struct { + Event *KeeperRegistryVBUpkeepRegistered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeeperRegistryVBUpkeepRegisteredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepRegistered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeeperRegistryVBUpkeepRegistered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeeperRegistryVBUpkeepRegisteredIterator) Error() error { + return it.fail +} + +func (it *KeeperRegistryVBUpkeepRegisteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeeperRegistryVBUpkeepRegistered struct { + Id *big.Int + ExecuteGas uint32 + Admin common.Address + Raw types.Log +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) FilterUpkeepRegistered(opts *bind.FilterOpts, id []*big.Int) (*KeeperRegistryVBUpkeepRegisteredIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.FilterLogs(opts, "UpkeepRegistered", idRule) + if err != nil { + return nil, err + } + return &KeeperRegistryVBUpkeepRegisteredIterator{contract: _KeeperRegistryVB.contract, event: "UpkeepRegistered", logs: logs, sub: sub}, nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) WatchUpkeepRegistered(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepRegistered, id []*big.Int) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _KeeperRegistryVB.contract.WatchLogs(opts, "UpkeepRegistered", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeeperRegistryVBUpkeepRegistered) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepRegistered", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeeperRegistryVB *KeeperRegistryVBFilterer) ParseUpkeepRegistered(log types.Log) (*KeeperRegistryVBUpkeepRegistered, error) { + event := new(KeeperRegistryVBUpkeepRegistered) + if err := _KeeperRegistryVB.contract.UnpackLog(event, "UpkeepRegistered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type GetConfig struct { + PaymentPremiumPPB uint32 + BlockCountPerTurn *big.Int + CheckGasLimit uint32 + StalenessSeconds *big.Int + GasCeilingMultiplier uint16 + FallbackGasPrice *big.Int + FallbackLinkPrice *big.Int +} +type GetKeeperInfo struct { + Payee common.Address + Active bool + Balance *big.Int +} +type GetUpkeep struct { + Target common.Address + ExecuteGas uint32 + CheckData []byte + Balance *big.Int + LastKeeper common.Address + Admin common.Address + MaxValidBlocknumber uint64 +} + +func (_KeeperRegistryVB *KeeperRegistryVB) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _KeeperRegistryVB.abi.Events["ConfigSet"].ID: + return _KeeperRegistryVB.ParseConfigSet(log) + case _KeeperRegistryVB.abi.Events["FlatFeeSet"].ID: + return _KeeperRegistryVB.ParseFlatFeeSet(log) + case _KeeperRegistryVB.abi.Events["FundsAdded"].ID: + return _KeeperRegistryVB.ParseFundsAdded(log) + case _KeeperRegistryVB.abi.Events["FundsWithdrawn"].ID: + return _KeeperRegistryVB.ParseFundsWithdrawn(log) + case _KeeperRegistryVB.abi.Events["KeepersUpdated"].ID: + return _KeeperRegistryVB.ParseKeepersUpdated(log) + case _KeeperRegistryVB.abi.Events["OwnershipTransferRequested"].ID: + return _KeeperRegistryVB.ParseOwnershipTransferRequested(log) + case _KeeperRegistryVB.abi.Events["OwnershipTransferred"].ID: + return _KeeperRegistryVB.ParseOwnershipTransferred(log) + case _KeeperRegistryVB.abi.Events["Paused"].ID: + return _KeeperRegistryVB.ParsePaused(log) + case _KeeperRegistryVB.abi.Events["PayeeshipTransferRequested"].ID: + return _KeeperRegistryVB.ParsePayeeshipTransferRequested(log) + case _KeeperRegistryVB.abi.Events["PayeeshipTransferred"].ID: + return _KeeperRegistryVB.ParsePayeeshipTransferred(log) + case _KeeperRegistryVB.abi.Events["PaymentWithdrawn"].ID: + return _KeeperRegistryVB.ParsePaymentWithdrawn(log) + case _KeeperRegistryVB.abi.Events["RegistrarChanged"].ID: + return _KeeperRegistryVB.ParseRegistrarChanged(log) + case _KeeperRegistryVB.abi.Events["Unpaused"].ID: + return _KeeperRegistryVB.ParseUnpaused(log) + case _KeeperRegistryVB.abi.Events["UpkeepCanceled"].ID: + return _KeeperRegistryVB.ParseUpkeepCanceled(log) + case _KeeperRegistryVB.abi.Events["UpkeepPerformed"].ID: + return _KeeperRegistryVB.ParseUpkeepPerformed(log) + case _KeeperRegistryVB.abi.Events["UpkeepRegistered"].ID: + return _KeeperRegistryVB.ParseUpkeepRegistered(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (KeeperRegistryVBConfigSet) Topic() common.Hash { + return common.HexToHash("0x6db8cdacf21c3bbd6135926f497c6fba81fd6969684ecf85f56550d2b1f8e691") +} + +func (KeeperRegistryVBFlatFeeSet) Topic() common.Hash { + return common.HexToHash("0x17b46a44a823646eef686b7824df2962de896bc9a012a60b67694c5cbf184d8b") +} + +func (KeeperRegistryVBFundsAdded) Topic() common.Hash { + return common.HexToHash("0xafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa734891506203") +} + +func (KeeperRegistryVBFundsWithdrawn) Topic() common.Hash { + return common.HexToHash("0xf3b5906e5672f3e524854103bcafbbdba80dbdfeca2c35e116127b1060a68318") +} + +func (KeeperRegistryVBKeepersUpdated) Topic() common.Hash { + return common.HexToHash("0x056264c94f28bb06c99d13f0446eb96c67c215d8d707bce2655a98ddf1c0b71f") +} + +func (KeeperRegistryVBOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (KeeperRegistryVBOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (KeeperRegistryVBPaused) Topic() common.Hash { + return common.HexToHash("0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258") +} + +func (KeeperRegistryVBPayeeshipTransferRequested) Topic() common.Hash { + return common.HexToHash("0x84f7c7c80bb8ed2279b4aab5f61cd05e6374073d38f46d7f32de8c30e9e38367") +} + +func (KeeperRegistryVBPayeeshipTransferred) Topic() common.Hash { + return common.HexToHash("0x78af32efdcad432315431e9b03d27e6cd98fb79c405fdc5af7c1714d9c0f75b3") +} + +func (KeeperRegistryVBPaymentWithdrawn) Topic() common.Hash { + return common.HexToHash("0x9819093176a1851202c7bcfa46845809b4e47c261866550e94ed3775d2f40698") +} + +func (KeeperRegistryVBRegistrarChanged) Topic() common.Hash { + return common.HexToHash("0x9bf4a5b30267728df68663e14adb47e559863967c419dc6030638883408bed2e") +} + +func (KeeperRegistryVBUnpaused) Topic() common.Hash { + return common.HexToHash("0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa") +} + +func (KeeperRegistryVBUpkeepCanceled) Topic() common.Hash { + return common.HexToHash("0x91cb3bb75cfbd718bbfccc56b7f53d92d7048ef4ca39a3b7b7c6d4af1f791181") +} + +func (KeeperRegistryVBUpkeepPerformed) Topic() common.Hash { + return common.HexToHash("0xcaacad83e47cc45c280d487ec84184eee2fa3b54ebaa393bda7549f13da228f6") +} + +func (KeeperRegistryVBUpkeepRegistered) Topic() common.Hash { + return common.HexToHash("0xbae366358c023f887e791d7a62f2e4316f1026bd77f6fb49501a917b3bc5d012") +} + +func (_KeeperRegistryVB *KeeperRegistryVB) Address() common.Address { + return _KeeperRegistryVB.address +} + +type KeeperRegistryVBInterface interface { + FASTGASFEED(opts *bind.CallOpts) (common.Address, error) + + LINK(opts *bind.CallOpts) (common.Address, error) + + LINKETHFEED(opts *bind.CallOpts) (common.Address, error) + + GetCanceledUpkeepList(opts *bind.CallOpts) ([]*big.Int, error) + + GetConfig(opts *bind.CallOpts) (GetConfig, + + error) + + GetFlatFee(opts *bind.CallOpts) (uint32, error) + + GetKeeperInfo(opts *bind.CallOpts, query common.Address) (GetKeeperInfo, + + error) + + GetKeeperList(opts *bind.CallOpts) ([]common.Address, error) + + GetMaxPaymentForGas(opts *bind.CallOpts, gasLimit *big.Int) (*big.Int, error) + + GetMinBalanceForUpkeep(opts *bind.CallOpts, id *big.Int) (*big.Int, error) + + GetMustTakeTurns(opts *bind.CallOpts) (bool, error) + + GetRegistrar(opts *bind.CallOpts) (common.Address, error) + + GetUpkeep(opts *bind.CallOpts, id *big.Int) (GetUpkeep, + + error) + + GetUpkeepCount(opts *bind.CallOpts) (*big.Int, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + Paused(opts *bind.CallOpts) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + AcceptPayeeship(opts *bind.TransactOpts, keeper common.Address) (*types.Transaction, error) + + AddFunds(opts *bind.TransactOpts, id *big.Int, amount *big.Int) (*types.Transaction, error) + + CancelUpkeep(opts *bind.TransactOpts, id *big.Int) (*types.Transaction, error) + + CheckUpkeep(opts *bind.TransactOpts, id *big.Int, from common.Address) (*types.Transaction, error) + + OnTokenTransfer(opts *bind.TransactOpts, sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) + + Pause(opts *bind.TransactOpts) (*types.Transaction, error) + + PerformUpkeep(opts *bind.TransactOpts, id *big.Int, performData []byte) (*types.Transaction, error) + + RecoverFunds(opts *bind.TransactOpts) (*types.Transaction, error) + + RegisterUpkeep(opts *bind.TransactOpts, target common.Address, gasLimit uint32, admin common.Address, checkData []byte) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int, mustTakeTurns bool) (*types.Transaction, error) + + SetKeepers(opts *bind.TransactOpts, keepers []common.Address, payees []common.Address) (*types.Transaction, error) + + SetRegistrar(opts *bind.TransactOpts, registrar common.Address) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + TransferPayeeship(opts *bind.TransactOpts, keeper common.Address, proposed common.Address) (*types.Transaction, error) + + Unpause(opts *bind.TransactOpts) (*types.Transaction, error) + + WithdrawFunds(opts *bind.TransactOpts, id *big.Int, to common.Address) (*types.Transaction, error) + + WithdrawPayment(opts *bind.TransactOpts, from common.Address, to common.Address) (*types.Transaction, error) + + FilterConfigSet(opts *bind.FilterOpts) (*KeeperRegistryVBConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBConfigSet) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*KeeperRegistryVBConfigSet, error) + + FilterFlatFeeSet(opts *bind.FilterOpts) (*KeeperRegistryVBFlatFeeSetIterator, error) + + WatchFlatFeeSet(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFlatFeeSet) (event.Subscription, error) + + ParseFlatFeeSet(log types.Log) (*KeeperRegistryVBFlatFeeSet, error) + + FilterFundsAdded(opts *bind.FilterOpts, id []*big.Int, from []common.Address) (*KeeperRegistryVBFundsAddedIterator, error) + + WatchFundsAdded(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFundsAdded, id []*big.Int, from []common.Address) (event.Subscription, error) + + ParseFundsAdded(log types.Log) (*KeeperRegistryVBFundsAdded, error) + + FilterFundsWithdrawn(opts *bind.FilterOpts, id []*big.Int) (*KeeperRegistryVBFundsWithdrawnIterator, error) + + WatchFundsWithdrawn(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBFundsWithdrawn, id []*big.Int) (event.Subscription, error) + + ParseFundsWithdrawn(log types.Log) (*KeeperRegistryVBFundsWithdrawn, error) + + FilterKeepersUpdated(opts *bind.FilterOpts) (*KeeperRegistryVBKeepersUpdatedIterator, error) + + WatchKeepersUpdated(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBKeepersUpdated) (event.Subscription, error) + + ParseKeepersUpdated(log types.Log) (*KeeperRegistryVBKeepersUpdated, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*KeeperRegistryVBOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*KeeperRegistryVBOwnershipTransferred, error) + + FilterPaused(opts *bind.FilterOpts) (*KeeperRegistryVBPausedIterator, error) + + WatchPaused(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPaused) (event.Subscription, error) + + ParsePaused(log types.Log) (*KeeperRegistryVBPaused, error) + + FilterPayeeshipTransferRequested(opts *bind.FilterOpts, keeper []common.Address, from []common.Address, to []common.Address) (*KeeperRegistryVBPayeeshipTransferRequestedIterator, error) + + WatchPayeeshipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPayeeshipTransferRequested, keeper []common.Address, from []common.Address, to []common.Address) (event.Subscription, error) + + ParsePayeeshipTransferRequested(log types.Log) (*KeeperRegistryVBPayeeshipTransferRequested, error) + + FilterPayeeshipTransferred(opts *bind.FilterOpts, keeper []common.Address, from []common.Address, to []common.Address) (*KeeperRegistryVBPayeeshipTransferredIterator, error) + + WatchPayeeshipTransferred(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPayeeshipTransferred, keeper []common.Address, from []common.Address, to []common.Address) (event.Subscription, error) + + ParsePayeeshipTransferred(log types.Log) (*KeeperRegistryVBPayeeshipTransferred, error) + + FilterPaymentWithdrawn(opts *bind.FilterOpts, keeper []common.Address, amount []*big.Int, to []common.Address) (*KeeperRegistryVBPaymentWithdrawnIterator, error) + + WatchPaymentWithdrawn(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBPaymentWithdrawn, keeper []common.Address, amount []*big.Int, to []common.Address) (event.Subscription, error) + + ParsePaymentWithdrawn(log types.Log) (*KeeperRegistryVBPaymentWithdrawn, error) + + FilterRegistrarChanged(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeeperRegistryVBRegistrarChangedIterator, error) + + WatchRegistrarChanged(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBRegistrarChanged, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseRegistrarChanged(log types.Log) (*KeeperRegistryVBRegistrarChanged, error) + + FilterUnpaused(opts *bind.FilterOpts) (*KeeperRegistryVBUnpausedIterator, error) + + WatchUnpaused(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUnpaused) (event.Subscription, error) + + ParseUnpaused(log types.Log) (*KeeperRegistryVBUnpaused, error) + + FilterUpkeepCanceled(opts *bind.FilterOpts, id []*big.Int, atBlockHeight []uint64) (*KeeperRegistryVBUpkeepCanceledIterator, error) + + WatchUpkeepCanceled(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepCanceled, id []*big.Int, atBlockHeight []uint64) (event.Subscription, error) + + ParseUpkeepCanceled(log types.Log) (*KeeperRegistryVBUpkeepCanceled, error) + + FilterUpkeepPerformed(opts *bind.FilterOpts, id []*big.Int, success []bool, from []common.Address) (*KeeperRegistryVBUpkeepPerformedIterator, error) + + WatchUpkeepPerformed(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepPerformed, id []*big.Int, success []bool, from []common.Address) (event.Subscription, error) + + ParseUpkeepPerformed(log types.Log) (*KeeperRegistryVBUpkeepPerformed, error) + + FilterUpkeepRegistered(opts *bind.FilterOpts, id []*big.Int) (*KeeperRegistryVBUpkeepRegisteredIterator, error) + + WatchUpkeepRegistered(opts *bind.WatchOpts, sink chan<- *KeeperRegistryVBUpkeepRegistered, id []*big.Int) (event.Subscription, error) + + ParseUpkeepRegistered(log types.Log) (*KeeperRegistryVBUpkeepRegistered, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/internal/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go b/core/internal/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go index 228862809cf..fe7c031af28 100644 --- a/core/internal/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go +++ b/core/internal/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go @@ -30,8 +30,8 @@ var ( ) var UpkeepCounterMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_testRange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_interval\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"initialBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"lastBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"counter\",\"type\":\"uint256\"}],\"name\":\"PerformingUpkeep\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"checkUpkeep\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eligible\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"interval\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"performUpkeep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_testRange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_interval\",\"type\":\"uint256\"}],\"name\":\"setSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"testRange\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b506040516104163803806104168339818101604052604081101561003357600080fd5b50805160209091015160009182556001554360025560038190556004556103b78061005f6000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c80636e04ff0d11610076578063806b984f1161005b578063806b984f14610258578063947a36fb14610260578063d832d92f14610268576100a3565b80636e04ff0d146101445780637f407edf14610235576100a3565b80632cb15864146100a85780634585e33b146100c257806361bc221a146101345780636250a13a1461013c575b600080fd5b6100b0610284565b60408051918252519081900360200190f35b610132600480360360208110156100d857600080fd5b8101906020810181356401000000008111156100f357600080fd5b82018360208201111561010557600080fd5b8035906020019184600183028401116401000000008311171561012757600080fd5b50909250905061028a565b005b6100b06102f8565b6100b06102fe565b6101b46004803603602081101561015a57600080fd5b81019060208101813564010000000081111561017557600080fd5b82018360208201111561018757600080fd5b803590602001918460018302840111640100000000831117156101a957600080fd5b509092509050610304565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156101f95781810151838201526020016101e1565b50505050905090810190601f1680156102265780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6101326004803603604081101561024b57600080fd5b5080359060200135610356565b6100b0610368565b6100b061036e565b610270610374565b604080519115158252519081900360200190f35b60035481565b60035461029657436003555b436002819055600480546001019081905560035460408051328152602081019290925281810193909352606081019190915290517f1313be6f6d6263f115d3e986c9622f868fcda43c8b8e7ef193e7a53d75a4d27c9181900360800190a15050565b60045481565b60005481565b60006060610310610374565b848481818080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250959a92995091975050505050505050565b60009182556001556003819055600455565b60025481565b60015481565b600060035460001415610389575060016103a7565b60005460035443031080156103a45750600154600254430310155b90505b9056fea164736f6c6343000706000a", + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_testRange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_interval\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"initialBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"lastBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"counter\",\"type\":\"uint256\"}],\"name\":\"PerformingUpkeep\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"checkUpkeep\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eligible\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"interval\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"performUpkeep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"previousPerformBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_testRange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_interval\",\"type\":\"uint256\"}],\"name\":\"setSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"testRange\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5060405161044d38038061044d8339818101604052604081101561003357600080fd5b508051602090910151600091825560015560038190554360025560048190556005556103e9806100646000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80637f407edf11610076578063917d895f1161005b578063917d895f1461027b578063947a36fb14610283578063d832d92f1461028b576100be565b80637f407edf14610250578063806b984f14610273576100be565b806361bc221a116100a757806361bc221a1461014f5780636250a13a146101575780636e04ff0d1461015f576100be565b80632cb15864146100c35780634585e33b146100dd575b600080fd5b6100cb6102a7565b60408051918252519081900360200190f35b61014d600480360360208110156100f357600080fd5b81019060208101813564010000000081111561010e57600080fd5b82018360208201111561012057600080fd5b8035906020019184600183028401116401000000008311171561014257600080fd5b5090925090506102ad565b005b6100cb610324565b6100cb61032a565b6101cf6004803603602081101561017557600080fd5b81019060208101813564010000000081111561019057600080fd5b8201836020820111156101a257600080fd5b803590602001918460018302840111640100000000831117156101c457600080fd5b509092509050610330565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156102145781810151838201526020016101fc565b50505050905090810190601f1680156102415780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b61014d6004803603604081101561026657600080fd5b5080359060200135610382565b6100cb610394565b6100cb61039a565b6100cb6103a0565b6102936103a6565b604080519115158252519081900360200190f35b60045481565b6004546102b957436004555b4360028190556005805460010190819055600454600354604080519283526020830194909452818401526060810191909152905132917f8e8112f20a2134e18e591d2cdd68cd86a95d06e6328ede501fc6314f4a5075fa919081900360800190a25050600254600355565b60055481565b60005481565b6000606061033c6103a6565b848481818080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250959a92995091975050505050505050565b60009182556001556004819055600555565b60025481565b60035481565b60015481565b6000600454600014156103bb575060016103d9565b60005460045443031080156103d65750600154600254430310155b90505b9056fea164736f6c6343000706000a", } var UpkeepCounterABI = UpkeepCounterMetaData.ABI @@ -303,6 +303,28 @@ func (_UpkeepCounter *UpkeepCounterCallerSession) LastBlock() (*big.Int, error) return _UpkeepCounter.Contract.LastBlock(&_UpkeepCounter.CallOpts) } +func (_UpkeepCounter *UpkeepCounterCaller) PreviousPerformBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _UpkeepCounter.contract.Call(opts, &out, "previousPerformBlock") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_UpkeepCounter *UpkeepCounterSession) PreviousPerformBlock() (*big.Int, error) { + return _UpkeepCounter.Contract.PreviousPerformBlock(&_UpkeepCounter.CallOpts) +} + +func (_UpkeepCounter *UpkeepCounterCallerSession) PreviousPerformBlock() (*big.Int, error) { + return _UpkeepCounter.Contract.PreviousPerformBlock(&_UpkeepCounter.CallOpts) +} + func (_UpkeepCounter *UpkeepCounterCaller) TestRange(opts *bind.CallOpts) (*big.Int, error) { var out []interface{} err := _UpkeepCounter.contract.Call(opts, &out, "testRange") @@ -410,25 +432,36 @@ func (it *UpkeepCounterPerformingUpkeepIterator) Close() error { } type UpkeepCounterPerformingUpkeep struct { - From common.Address - InitialBlock *big.Int - LastBlock *big.Int - Counter *big.Int - Raw types.Log + From common.Address + InitialBlock *big.Int + LastBlock *big.Int + PreviousBlock *big.Int + Counter *big.Int + Raw types.Log } -func (_UpkeepCounter *UpkeepCounterFilterer) FilterPerformingUpkeep(opts *bind.FilterOpts) (*UpkeepCounterPerformingUpkeepIterator, error) { +func (_UpkeepCounter *UpkeepCounterFilterer) FilterPerformingUpkeep(opts *bind.FilterOpts, from []common.Address) (*UpkeepCounterPerformingUpkeepIterator, error) { - logs, sub, err := _UpkeepCounter.contract.FilterLogs(opts, "PerformingUpkeep") + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _UpkeepCounter.contract.FilterLogs(opts, "PerformingUpkeep", fromRule) if err != nil { return nil, err } return &UpkeepCounterPerformingUpkeepIterator{contract: _UpkeepCounter.contract, event: "PerformingUpkeep", logs: logs, sub: sub}, nil } -func (_UpkeepCounter *UpkeepCounterFilterer) WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *UpkeepCounterPerformingUpkeep) (event.Subscription, error) { +func (_UpkeepCounter *UpkeepCounterFilterer) WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *UpkeepCounterPerformingUpkeep, from []common.Address) (event.Subscription, error) { - logs, sub, err := _UpkeepCounter.contract.WatchLogs(opts, "PerformingUpkeep") + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _UpkeepCounter.contract.WatchLogs(opts, "PerformingUpkeep", fromRule) if err != nil { return nil, err } @@ -480,7 +513,7 @@ func (_UpkeepCounter *UpkeepCounter) ParseLog(log types.Log) (generated.AbigenLo } func (UpkeepCounterPerformingUpkeep) Topic() common.Hash { - return common.HexToHash("0x1313be6f6d6263f115d3e986c9622f868fcda43c8b8e7ef193e7a53d75a4d27c") + return common.HexToHash("0x8e8112f20a2134e18e591d2cdd68cd86a95d06e6328ede501fc6314f4a5075fa") } func (_UpkeepCounter *UpkeepCounter) Address() common.Address { @@ -500,15 +533,17 @@ type UpkeepCounterInterface interface { LastBlock(opts *bind.CallOpts) (*big.Int, error) + PreviousPerformBlock(opts *bind.CallOpts) (*big.Int, error) + TestRange(opts *bind.CallOpts) (*big.Int, error) PerformUpkeep(opts *bind.TransactOpts, performData []byte) (*types.Transaction, error) SetSpread(opts *bind.TransactOpts, _testRange *big.Int, _interval *big.Int) (*types.Transaction, error) - FilterPerformingUpkeep(opts *bind.FilterOpts) (*UpkeepCounterPerformingUpkeepIterator, error) + FilterPerformingUpkeep(opts *bind.FilterOpts, from []common.Address) (*UpkeepCounterPerformingUpkeepIterator, error) - WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *UpkeepCounterPerformingUpkeep) (event.Subscription, error) + WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *UpkeepCounterPerformingUpkeep, from []common.Address) (event.Subscription, error) ParsePerformingUpkeep(log types.Log) (*UpkeepCounterPerformingUpkeep, error) diff --git a/core/internal/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go b/core/internal/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go index b81e08322cf..3d1906d40ee 100644 --- a/core/internal/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go +++ b/core/internal/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go @@ -28,7 +28,7 @@ var ( ) var VRFConsumerV2MetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"minReqConfs\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"minReqConfs\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Bin: "0x60a060405234801561001057600080fd5b50604051610e8d380380610e8d83398101604081905261002f9161008e565b6001600160601b0319606083901b16608052600280546001600160a01b03199081166001600160a01b0394851617909155600380549290931691161790556100c1565b80516001600160a01b038116811461008957600080fd5b919050565b600080604083850312156100a157600080fd5b6100aa83610072565b91506100b860208401610072565b90509250929050565b60805160601c610da76100e66000396000818161019e01526102060152610da76000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c80636802f72611610076578063e89e106a1161005b578063e89e106a14610161578063f08c5daa1461016a578063f6eaffc81461017357600080fd5b80636802f72614610109578063706da1ca1461011c57600080fd5b80631fe543e3146100a857806327784fad146100bd5780632fa4e442146100e357806336bfffed146100f6575b600080fd5b6100bb6100b6366004610abb565b610186565b005b6100d06100cb366004610a20565b610246565b6040519081526020015b60405180910390f35b6100bb6100f1366004610b7c565b610328565b6100bb610104366004610938565b610488565b6100bb610117366004610b7c565b610610565b6003546101489074010000000000000000000000000000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016100da565b6100d060015481565b6100d060045481565b6100d0610181366004610a89565b610817565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610238576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b6102428282610838565b5050565b6002546040517f5d3b1d300000000000000000000000000000000000000000000000000000000081526004810187905267ffffffffffffffff8616602482015261ffff8516604482015263ffffffff80851660648301528316608482015260009173ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b1580156102e157600080fd5b505af11580156102f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103199190610aa2565b60018190559695505050505050565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff166103b3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f737562206e6f7420736574000000000000000000000000000000000000000000604482015260640161022f565b6003546002546040805174010000000000000000000000000000000000000000840467ffffffffffffffff16602082015273ffffffffffffffffffffffffffffffffffffffff93841693634000aea09316918591015b6040516020818303038152906040526040518463ffffffff1660e01b815260040161043693929190610baa565b602060405180830381600087803b15801561045057600080fd5b505af1158015610464573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061024291906109f7565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff16610513576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f7375624944206e6f742073657400000000000000000000000000000000000000604482015260640161022f565b60005b815181101561024257600254600354835173ffffffffffffffffffffffffffffffffffffffff90921691637341c10c9174010000000000000000000000000000000000000000900467ffffffffffffffff169085908590811061057b5761057b610d23565b60200260200101516040518363ffffffff1660e01b81526004016105cb92919067ffffffffffffffff92909216825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b1580156105e557600080fd5b505af11580156105f9573d6000803e3d6000fd5b50505050808061060890610cc3565b915050610516565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff166103b357600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a21a23e46040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156106a357600080fd5b505af11580156106b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106db9190610b5f565b600380547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000067ffffffffffffffff938416810291909117918290556002546040517f7341c10c00000000000000000000000000000000000000000000000000000000815291909204909216600483015230602483015273ffffffffffffffffffffffffffffffffffffffff1690637341c10c90604401600060405180830381600087803b1580156107a257600080fd5b505af11580156107b6573d6000803e3d6000fd5b50506003546002546040805174010000000000000000000000000000000000000000840467ffffffffffffffff16602082015273ffffffffffffffffffffffffffffffffffffffff9384169550634000aea094509290911691859101610409565b6000818154811061082757600080fd5b600091825260209091200154905081565b60015482146108a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265717565737420494420697320696e636f7272656374000000000000000000604482015260640161022f565b5a60045580516108ba9060009060208401906108bf565b505050565b8280548282559060005260206000209081019282156108fa579160200282015b828111156108fa5782518255916020019190600101906108df565b5061090692915061090a565b5090565b5b80821115610906576000815560010161090b565b803563ffffffff8116811461093357600080fd5b919050565b6000602080838503121561094b57600080fd5b823567ffffffffffffffff81111561096257600080fd5b8301601f8101851361097357600080fd5b803561098661098182610c9f565b610c50565b80828252848201915084840188868560051b87010111156109a657600080fd5b60009450845b848110156109e957813573ffffffffffffffffffffffffffffffffffffffff811681146109d7578687fd5b845292860192908601906001016109ac565b509098975050505050505050565b600060208284031215610a0957600080fd5b81518015158114610a1957600080fd5b9392505050565b600080600080600060a08688031215610a3857600080fd5b853594506020860135610a4a81610d81565b9350604086013561ffff81168114610a6157600080fd5b9250610a6f6060870161091f565b9150610a7d6080870161091f565b90509295509295909350565b600060208284031215610a9b57600080fd5b5035919050565b600060208284031215610ab457600080fd5b5051919050565b60008060408385031215610ace57600080fd5b8235915060208084013567ffffffffffffffff811115610aed57600080fd5b8401601f81018613610afe57600080fd5b8035610b0c61098182610c9f565b80828252848201915084840189868560051b8701011115610b2c57600080fd5b600094505b83851015610b4f578035835260019490940193918501918501610b31565b5080955050505050509250929050565b600060208284031215610b7157600080fd5b8151610a1981610d81565b600060208284031215610b8e57600080fd5b81356bffffffffffffffffffffffff81168114610a1957600080fd5b73ffffffffffffffffffffffffffffffffffffffff84168152600060206bffffffffffffffffffffffff85168184015260606040840152835180606085015260005b81811015610c0857858101830151858201608001528201610bec565b81811115610c1a576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610c9757610c97610d52565b604052919050565b600067ffffffffffffffff821115610cb957610cb9610d52565b5060051b60200190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610d1c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81168114610d9757600080fd5b5056fea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go b/core/internal/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go index 1dcf9516fc2..08c4408c602 100644 --- a/core/internal/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go +++ b/core/internal/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go @@ -62,7 +62,7 @@ type VRFProof struct { } var VRFCoordinatorV2MetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"blockhashStore\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"linkEthFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"indexed\":false,\"internalType\":\"structVRFCoordinatorV2.FeeConfig\",\"name\":\"feeConfig\",\"type\":\"tuple\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FundsRecovered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"}],\"name\":\"ProvingKeyDeregistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"}],\"name\":\"ProvingKeyRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"outputSeed\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"RandomWordsFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"preSeed\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RandomWordsRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SubscriptionCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"SubscriptionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"SubscriptionFunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BLOCKHASH_STORE\",\"outputs\":[{\"internalType\":\"contractBlockhashStoreInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK\",\"outputs\":[{\"internalType\":\"contractLinkTokenInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK_ETH_FEED\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_CONSUMERS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_NUM_WORDS\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_REQUEST_CONFIRMATIONS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"acceptSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"addConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"cancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"createSubscription\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"}],\"name\":\"deregisterProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"pk\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"gamma\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"c\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"s\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"seed\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"uWitness\",\"type\":\"address\"},{\"internalType\":\"uint256[2]\",\"name\":\"cGammaWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"sHashWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"zInv\",\"type\":\"uint256\"}],\"internalType\":\"structVRF.Proof\",\"name\":\"proof\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"blockNum\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"structVRFCoordinatorV2.RequestCommitment\",\"name\":\"rc\",\"type\":\"tuple\"}],\"name\":\"fulfillRandomWords\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"}],\"name\":\"getCommitment\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentSubId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFallbackWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFeeConfig\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"reqCount\",\"type\":\"uint64\"}],\"name\":\"getFeeTier\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRequestConfig\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"getSubscription\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"balance\",\"type\":\"uint96\"},{\"internalType\":\"uint64\",\"name\":\"reqCount\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTotalBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicKey\",\"type\":\"uint256[2]\"}],\"name\":\"hashOfKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"ownerCancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"pendingRequestExists\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"recoverFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"},{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"}],\"name\":\"registerProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"removeConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"requestRandomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"requestSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"internalType\":\"structVRFCoordinatorV2.FeeConfig\",\"name\":\"feeConfig\",\"type\":\"tuple\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"blockhashStore\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"linkEthFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"internalBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"externalBalance\",\"type\":\"uint256\"}],\"name\":\"BalanceInvariantViolated\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNum\",\"type\":\"uint256\"}],\"name\":\"BlockhashNotInStore\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"have\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"want\",\"type\":\"uint32\"}],\"name\":\"GasLimitTooBig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IncorrectCommitment\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"have\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"want\",\"type\":\"uint256\"}],\"name\":\"InsufficientGasForConsumer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"InvalidConsumer\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"have\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"min\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"max\",\"type\":\"uint16\"}],\"name\":\"InvalidRequestConfirmations\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"proposedOwner\",\"type\":\"address\"}],\"name\":\"MustBeRequestedOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoCorrespondingRequest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"NoSuchProvingKey\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"have\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"want\",\"type\":\"uint32\"}],\"name\":\"NumWordsTooBig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableFromLink\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PendingRequestExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"ProvingKeyAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Reentrant\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManyConsumers\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"indexed\":false,\"internalType\":\"structVRFCoordinatorV2.FeeConfig\",\"name\":\"feeConfig\",\"type\":\"tuple\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FundsRecovered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"}],\"name\":\"ProvingKeyDeregistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"}],\"name\":\"ProvingKeyRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"outputSeed\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"name\":\"RandomWordsFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"preSeed\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RandomWordsRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SubscriptionCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"SubscriptionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"SubscriptionFunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BLOCKHASH_STORE\",\"outputs\":[{\"internalType\":\"contractBlockhashStoreInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK\",\"outputs\":[{\"internalType\":\"contractLinkTokenInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK_ETH_FEED\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_CONSUMERS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_NUM_WORDS\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_REQUEST_CONFIRMATIONS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"acceptSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"addConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"cancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"createSubscription\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"}],\"name\":\"deregisterProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"pk\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"gamma\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"c\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"s\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"seed\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"uWitness\",\"type\":\"address\"},{\"internalType\":\"uint256[2]\",\"name\":\"cGammaWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"sHashWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"zInv\",\"type\":\"uint256\"}],\"internalType\":\"structVRF.Proof\",\"name\":\"proof\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"blockNum\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"structVRFCoordinatorV2.RequestCommitment\",\"name\":\"rc\",\"type\":\"tuple\"}],\"name\":\"fulfillRandomWords\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"}],\"name\":\"getCommitment\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentSubId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFallbackWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFeeConfig\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"reqCount\",\"type\":\"uint64\"}],\"name\":\"getFeeTier\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRequestConfig\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"getSubscription\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"balance\",\"type\":\"uint96\"},{\"internalType\":\"uint64\",\"name\":\"reqCount\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTotalBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicKey\",\"type\":\"uint256[2]\"}],\"name\":\"hashOfKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"ownerCancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"}],\"name\":\"pendingRequestExists\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"recoverFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracle\",\"type\":\"address\"},{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"}],\"name\":\"registerProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"removeConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"requestRandomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"requestSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier1\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier2\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier3\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier4\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkPPMTier5\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier2\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier3\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier4\",\"type\":\"uint24\"},{\"internalType\":\"uint24\",\"name\":\"reqsForTier5\",\"type\":\"uint24\"}],\"internalType\":\"structVRFCoordinatorV2.FeeConfig\",\"name\":\"feeConfig\",\"type\":\"tuple\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", Bin: "0x60e06040523480156200001157600080fd5b506040516200608c3803806200608c8339810160408190526200003491620001b1565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000e8565b5050506001600160601b0319606093841b811660805290831b811660a052911b1660c052620001fb565b6001600160a01b038116331415620001435760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b0381168114620001ac57600080fd5b919050565b600080600060608486031215620001c757600080fd5b620001d28462000194565b9250620001e26020850162000194565b9150620001f26040850162000194565b90509250925092565b60805160601c60a05160601c60c05160601c615e2762000265600039600081816105260152613bd901526000818161061d015261402401526000818161036d01528181611599015281816125960152818161302c0152818161318201526138360152615e276000f3fe608060405234801561001057600080fd5b506004361061025b5760003560e01c80636f64f03f11610145578063ad178361116100bd578063d2f9f9a71161008c578063e72f6e3011610071578063e72f6e30146106fa578063e82ad7d41461070d578063f2fde38b1461073057600080fd5b8063d2f9f9a7146106d4578063d7ae1d30146106e757600080fd5b8063ad17836114610618578063af198b971461063f578063c3f909d41461066f578063caf70c4a146106c157600080fd5b80638da5cb5b11610114578063a21a23e4116100f9578063a21a23e4146105da578063a47c7696146105e2578063a4c0ed361461060557600080fd5b80638da5cb5b146105a95780639f87fad7146105c757600080fd5b80636f64f03f146105685780637341c10c1461057b57806379ba50971461058e578063823597401461059657600080fd5b8063356dac71116101d85780635fbbc0d2116101a757806366316d8d1161018c57806366316d8d1461050e578063689c45171461052157806369bcdb7d1461054857600080fd5b80635fbbc0d21461040057806364d51a2a1461050657600080fd5b8063356dac71146103b457806340d6bb82146103bc5780634cb48a54146103da5780635d3b1d30146103ed57600080fd5b806308821d581161022f57806315c48b841161021457806315c48b841461030e578063181f5a77146103295780631b6b6d231461036857600080fd5b806308821d58146102cf57806312b58349146102e257600080fd5b80620122911461026057806302bcc5b61461028057806304c357cb1461029557806306bfa637146102a8575b600080fd5b610268610743565b60405161027793929190615964565b60405180910390f35b61029361028e366004615792565b6107bf565b005b6102936102a33660046157ad565b61086b565b60055467ffffffffffffffff165b60405167ffffffffffffffff9091168152602001610277565b6102936102dd3660046154a3565b610a60565b6005546801000000000000000090046bffffffffffffffffffffffff165b604051908152602001610277565b61031660c881565b60405161ffff9091168152602001610277565b604080518082018252601681527f565246436f6f7264696e61746f72563220312e302e30000000000000000000006020820152905161027791906158f1565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610277565b600a54610300565b6103c56101f481565b60405163ffffffff9091168152602001610277565b6102936103e836600461563c565b610c3f565b6103006103fb366004615516565b611036565b600c546040805163ffffffff80841682526401000000008404811660208301526801000000000000000084048116928201929092526c010000000000000000000000008304821660608201527001000000000000000000000000000000008304909116608082015262ffffff740100000000000000000000000000000000000000008304811660a0830152770100000000000000000000000000000000000000000000008304811660c08301527a0100000000000000000000000000000000000000000000000000008304811660e08301527d01000000000000000000000000000000000000000000000000000000000090920490911661010082015261012001610277565b610316606481565b61029361051c36600461545b565b611444565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b610300610556366004615779565b60009081526009602052604090205490565b6102936105763660046153a0565b6116ad565b6102936105893660046157ad565b6117f7565b610293611a85565b6102936105a4366004615792565b611b82565b60005473ffffffffffffffffffffffffffffffffffffffff1661038f565b6102936105d53660046157ad565b611d7c565b6102b66121fd565b6105f56105f0366004615792565b6123ed565b6040516102779493929190615b02565b6102936106133660046153d4565b612537565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b61065261064d366004615574565b6127a8565b6040516bffffffffffffffffffffffff9091168152602001610277565b600b546040805161ffff8316815263ffffffff6201000084048116602083015267010000000000000084048116928201929092526b010000000000000000000000909204166060820152608001610277565b6103006106cf3660046154bf565b612c6d565b6103c56106e2366004615792565b612c9d565b6102936106f53660046157ad565b612e92565b610293610708366004615385565b612ff3565b61072061071b366004615792565b613257565b6040519015158152602001610277565b61029361073e366004615385565b6134ae565b600b546007805460408051602080840282018101909252828152600094859460609461ffff8316946201000090930463ffffffff169391928391908301828280156107ad57602002820191906000526020600020905b815481526020019060010190808311610799575b50505050509050925092509250909192565b6107c76134bf565b67ffffffffffffffff811660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1661082d576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090205461086890829073ffffffffffffffffffffffffffffffffffffffff16613542565b50565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff16806108d4576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614610940576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024015b60405180910390fd5b600b546601000000000000900460ff1615610987576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff841660009081526003602052604090206001015473ffffffffffffffffffffffffffffffffffffffff848116911614610a5a5767ffffffffffffffff841660008181526003602090815260409182902060010180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88169081179091558251338152918201527f69436ea6df009049404f564eff6622cd00522b0bd6a89efd9e52a355c4a879be91015b60405180910390a25b50505050565b610a686134bf565b604080518082018252600091610a97919084906002908390839080828437600092019190915250612c6d915050565b60008181526006602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1680610af9576040517f77f5b84c00000000000000000000000000000000000000000000000000000000815260048101839052602401610937565b600082815260066020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555b600754811015610be9578260078281548110610b4c57610b4c615dbc565b90600052602060002001541415610bd7576007805460009190610b7190600190615c76565b81548110610b8157610b81615dbc565b906000526020600020015490508060078381548110610ba257610ba2615dbc565b6000918252602090912001556007805480610bbf57610bbf615d8d565b60019003818190600052602060002001600090559055505b80610be181615cba565b915050610b2e565b508073ffffffffffffffffffffffffffffffffffffffff167f72be339577868f868798bac2c93e52d6f034fef4689a9848996c14ebb7416c0d83604051610c3291815260200190565b60405180910390a2505050565b610c476134bf565b60c861ffff87161115610c9a576040517fa738697600000000000000000000000000000000000000000000000000000000815261ffff871660048201819052602482015260c86044820152606401610937565b60008213610cd7576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610937565b6040805160a0808201835261ffff891680835263ffffffff89811660208086018290526000868801528a831660608088018290528b85166080988901819052600b80547fffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000001690971762010000909502949094177fffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff166701000000000000009092027fffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffff16919091176b010000000000000000000000909302929092179093558651600c80549489015189890151938a0151978a0151968a015160c08b015160e08c01516101008d01519588167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009099169890981764010000000093881693909302929092177fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff1668010000000000000000958716959095027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16949094176c0100000000000000000000000098861698909802979097177fffffffffffffffffff00000000000000ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000096909416959095027fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16929092177401000000000000000000000000000000000000000062ffffff92831602177fffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffff1677010000000000000000000000000000000000000000000000958216959095027fffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffff16949094177a01000000000000000000000000000000000000000000000000000092851692909202919091177cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167d0100000000000000000000000000000000000000000000000000000000009390911692909202919091178155600a84905590517fc21e3bd2e0b339d2848f0dd956947a88966c242c0c0c582a33137a5c1ceb5cb2916110269189918991899189918991906159c3565b60405180910390a1505050505050565b600b546000906601000000000000900460ff1615611080576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff851660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff166110e6576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260026020908152604080832067ffffffffffffffff808a1685529252909120541680611156576040517ff0019fe600000000000000000000000000000000000000000000000000000000815267ffffffffffffffff87166004820152336024820152604401610937565b600b5461ffff9081169086161080611172575060c861ffff8616115b156111c257600b546040517fa738697600000000000000000000000000000000000000000000000000000000815261ffff8088166004830152909116602482015260c86044820152606401610937565b600b5463ffffffff620100009091048116908516111561122957600b546040517ff5d7e01e00000000000000000000000000000000000000000000000000000000815263ffffffff8087166004830152620100009092049091166024820152604401610937565b6101f463ffffffff8416111561127b576040517f47386bec00000000000000000000000000000000000000000000000000000000815263ffffffff841660048201526101f46024820152604401610937565b6000611288826001615bd2565b6040805160208082018c9052338284015267ffffffffffffffff808c16606084015284166080808401919091528351808403909101815260a08301845280519082012060c083018d905260e080840182905284518085039091018152610100909301909352815191012091925060009182916040805160208101849052439181019190915267ffffffffffffffff8c16606082015263ffffffff808b166080830152891660a08201523360c0820152919350915060e001604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152828252805160209182012060008681526009835283902055848352820183905261ffff8a169082015263ffffffff808916606083015287166080820152339067ffffffffffffffff8b16908c907f63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a97729060a00160405180910390a45033600090815260026020908152604080832067ffffffffffffffff808d16855292529091208054919093167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009091161790915591505095945050505050565b600b546601000000000000900460ff161561148b576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600860205260409020546bffffffffffffffffffffffff808316911610156114e5576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260086020526040812080548392906115129084906bffffffffffffffffffffffff16615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555080600560088282829054906101000a90046bffffffffffffffffffffffff166115699190615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b815260040161162192919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b602060405180830381600087803b15801561163b57600080fd5b505af115801561164f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061167391906154db565b6116a9576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050565b6116b56134bf565b6040805180820182526000916116e4919084906002908390839080828437600092019190915250612c6d915050565b60008181526006602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1615611746576040517f4a0b8fa700000000000000000000000000000000000000000000000000000000815260048101829052602401610937565b600081815260066020908152604080832080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88169081179091556007805460018101825594527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688909301849055518381527fe729ae16526293f74ade739043022254f1489f616295a25bf72dfb4511ed73b89101610c32565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680611860576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff8216146118c7576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff161561190e576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff841660009081526003602052604090206002015460641415611965576040517f05a48e0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260026020908152604080832067ffffffffffffffff808916855292529091205416156119ac57610a5a565b73ffffffffffffffffffffffffffffffffffffffff8316600081815260026020818152604080842067ffffffffffffffff8a1680865290835281852080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001908117909155600384528286209094018054948501815585529382902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001685179055905192835290917f43dc749a04ac8fb825cbd514f7c0e13f13bc6f2ee66043b76629d51776cff8e09101610a51565b60015473ffffffffffffffffffffffffffffffffffffffff163314611b06576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610937565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b600b546601000000000000900460ff1615611bc9576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff16611c2f576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090206001015473ffffffffffffffffffffffffffffffffffffffff163314611cd15767ffffffffffffffff8116600090815260036020526040908190206001015490517fd084e97500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610937565b67ffffffffffffffff81166000818152600360209081526040918290208054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560019093018054909316909255835173ffffffffffffffffffffffffffffffffffffffff909116808252928101919091529092917f6f1dc65165ffffedfd8e507b4a0f1fcfdada045ed11f6c26ba27cedfe87802f0910160405180910390a25050565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680611de5576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614611e4c576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff1615611e93576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260026020908152604080832067ffffffffffffffff808916855292529091205416611f2e576040517ff0019fe600000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff84166024820152604401610937565b67ffffffffffffffff8416600090815260036020908152604080832060020180548251818502810185019093528083529192909190830182828015611fa957602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311611f7e575b50505050509050600060018251611fc09190615c76565b905060005b825181101561215f578573ffffffffffffffffffffffffffffffffffffffff16838281518110611ff757611ff7615dbc565b602002602001015173ffffffffffffffffffffffffffffffffffffffff16141561214d57600083838151811061202f5761202f615dbc565b6020026020010151905080600360008a67ffffffffffffffff1667ffffffffffffffff168152602001908152602001600020600201838154811061207557612075615dbc565b600091825260208083209190910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff949094169390931790925567ffffffffffffffff8a1681526003909152604090206002018054806120ef576120ef615d8d565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190555061215f565b8061215781615cba565b915050611fc5565b5073ffffffffffffffffffffffffffffffffffffffff8516600081815260026020908152604080832067ffffffffffffffff8b168085529083529281902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001690555192835290917f182bff9831466789164ca77075fffd84916d35a8180ba73c27e45634549b445b91015b60405180910390a2505050505050565b600b546000906601000000000000900460ff1615612247576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005805467ffffffffffffffff1690600061226183615cf3565b82546101009290920a67ffffffffffffffff8181021990931691831602179091556005541690506000806040519080825280602002602001820160405280156122b4578160200160208202803683370190505b506040805180820182526000808252602080830182815267ffffffffffffffff888116808552600484528685209551865493516bffffffffffffffffffffffff9091167fffffffffffffffffffffffff0000000000000000000000000000000000000000948516176c010000000000000000000000009190931602919091179094558451606081018652338152808301848152818701888152958552600384529590932083518154831673ffffffffffffffffffffffffffffffffffffffff918216178255955160018201805490931696169590951790559151805194955090936123a592600285019201906150c5565b505060405133815267ffffffffffffffff841691507f464722b4166576d3dcbba877b999bc35cf911f4eaf434b7eba68fa113951d0bf9060200160405180910390a250905090565b67ffffffffffffffff81166000908152600360205260408120548190819060609073ffffffffffffffffffffffffffffffffffffffff1661245a576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff80861660009081526004602090815260408083205460038352928190208054600290910180548351818602810186019094528084526bffffffffffffffffffffffff8616966c010000000000000000000000009096049095169473ffffffffffffffffffffffffffffffffffffffff90921693909291839183018282801561252157602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116124f6575b5050505050905093509350935093509193509193565b600b546601000000000000900460ff161561257e576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146125ed576040517f44b0e3c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208114612627576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061263582840184615792565b67ffffffffffffffff811660009081526003602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1661269e576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff8116600090815260046020526040812080546bffffffffffffffffffffffff16918691906126d58385615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555084600560088282829054906101000a90046bffffffffffffffffffffffff1661272c9190615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508167ffffffffffffffff167fd39ec07f4e209f627a4c427971473820dc129761ba28de8906bd56f57101d4f88287846127939190615bba565b604080519283526020830191909152016121ed565b600b546000906601000000000000900460ff16156127f2576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005a9050600080600061280687876139b5565b9250925092506000866060015163ffffffff1667ffffffffffffffff81111561283157612831615deb565b60405190808252806020026020018201604052801561285a578160200160208202803683370190505b50905060005b876060015163ffffffff168110156128ce5760408051602081018590529081018290526060016040516020818303038152906040528051906020012060001c8282815181106128b1576128b1615dbc565b6020908102919091010152806128c681615cba565b915050612860565b506000838152600960205260408082208290555181907f1fe543e300000000000000000000000000000000000000000000000000000000906129169087908690602401615ab4565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252600b80547fffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffff166601000000000000179055908a015160808b01519192506000916129e49163ffffffff169084613d04565b600b80547fffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffff1690556020808c01805167ffffffffffffffff9081166000908152600490935260408084205492518216845290922080549394506c01000000000000000000000000918290048316936001939192600c92612a68928692900416615bd2565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506000612abf8a600b600001600b9054906101000a900463ffffffff1663ffffffff16612ab985612c9d565b3a613d52565b6020808e015167ffffffffffffffff166000908152600490915260409020549091506bffffffffffffffffffffffff80831691161015612b2b576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808d015167ffffffffffffffff1660009081526004909152604081208054839290612b679084906bffffffffffffffffffffffff16615c8d565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008b81526006602090815260408083205473ffffffffffffffffffffffffffffffffffffffff1683526008909152812080548594509092612bd091859116615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550877f7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4888386604051612c53939291909283526bffffffffffffffffffffffff9190911660208301521515604082015260600190565b60405180910390a299505050505050505050505b92915050565b600081604051602001612c8091906158e3565b604051602081830303815290604052805190602001209050919050565b6040805161012081018252600c5463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c010000000000000000000000008104831660608301527001000000000000000000000000000000008104909216608082015262ffffff740100000000000000000000000000000000000000008304811660a08301819052770100000000000000000000000000000000000000000000008404821660c08401527a0100000000000000000000000000000000000000000000000000008404821660e08401527d0100000000000000000000000000000000000000000000000000000000009093041661010082015260009167ffffffffffffffff841611612dbb575192915050565b8267ffffffffffffffff168160a0015162ffffff16108015612df057508060c0015162ffffff168367ffffffffffffffff1611155b15612dff576020015192915050565b8267ffffffffffffffff168160c0015162ffffff16108015612e3457508060e0015162ffffff168367ffffffffffffffff1611155b15612e43576040015192915050565b8267ffffffffffffffff168160e0015162ffffff16108015612e79575080610100015162ffffff168367ffffffffffffffff1611155b15612e88576060015192915050565b6080015192915050565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680612efb576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614612f62576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff1615612fa9576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612fb284613257565b15612fe9576040517fb42f66e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610a5a8484613542565b612ffb6134bf565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a082319060240160206040518083038186803b15801561308357600080fd5b505afa158015613097573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130bb91906154fd565b6005549091506801000000000000000090046bffffffffffffffffffffffff168181111561311f576040517fa99da3020000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610937565b818110156132525760006131338284615c76565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152602482018390529192507f00000000000000000000000000000000000000000000000000000000000000009091169063a9059cbb90604401602060405180830381600087803b1580156131c857600080fd5b505af11580156131dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320091906154db565b506040805173ffffffffffffffffffffffffffffffffffffffff86168152602081018390527f59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b436600910160405180910390a1505b505050565b67ffffffffffffffff811660009081526003602090815260408083208151606081018352815473ffffffffffffffffffffffffffffffffffffffff9081168252600183015416818501526002820180548451818702810187018652818152879693958601939092919083018282801561330657602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116132db575b505050505081525050905060005b8160400151518110156134a45760005b60075481101561349157600061345a6007838154811061334657613346615dbc565b90600052602060002001548560400151858151811061336757613367615dbc565b602002602001015188600260008960400151898151811061338a5761338a615dbc565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040908101600090812067ffffffffffffffff808f168352935220541660408051602080820187905273ffffffffffffffffffffffffffffffffffffffff959095168183015267ffffffffffffffff9384166060820152919092166080808301919091528251808303909101815260a08201835280519084012060c082019490945260e080820185905282518083039091018152610100909101909152805191012091565b506000818152600960205260409020549091501561347e5750600195945050505050565b508061348981615cba565b915050613324565b508061349c81615cba565b915050613314565b5060009392505050565b6134b66134bf565b61086881613e5a565b60005473ffffffffffffffffffffffffffffffffffffffff163314613540576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610937565b565b600b546601000000000000900460ff1615613589576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff821660009081526003602090815260408083208151606081018352815473ffffffffffffffffffffffffffffffffffffffff90811682526001830154168185015260028201805484518187028101870186528181529295939486019383018282801561363457602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311613609575b5050509190925250505067ffffffffffffffff80851660009081526004602090815260408083208151808301909252546bffffffffffffffffffffffff81168083526c01000000000000000000000000909104909416918101919091529293505b83604001515181101561373b5760026000856040015183815181106136bc576136bc615dbc565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040908101600090812067ffffffffffffffff8a168252909252902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001690558061373381615cba565b915050613695565b5067ffffffffffffffff8516600090815260036020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081168255600182018054909116905590613796600283018261514f565b505067ffffffffffffffff8516600090815260046020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055600580548291906008906138069084906801000000000000000090046bffffffffffffffffffffffff16615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85836bffffffffffffffffffffffff166040518363ffffffff1660e01b81526004016138be92919073ffffffffffffffffffffffffffffffffffffffff929092168252602082015260400190565b602060405180830381600087803b1580156138d857600080fd5b505af11580156138ec573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061391091906154db565b613946576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff861681526bffffffffffffffffffffffff8316602082015267ffffffffffffffff8716917fe8ed5b475a5b5987aa9165e8731bb78043f39eee32ec5a1169a89e27fcd49815910160405180910390a25050505050565b60008060006139c78560000151612c6d565b60008181526006602052604090205490935073ffffffffffffffffffffffffffffffffffffffff1680613a29576040517f77f5b84c00000000000000000000000000000000000000000000000000000000815260048101859052602401610937565b6080860151604051613a48918691602001918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291815281516020928301206000818152600990935291205490935080613ac5576040517f3688124a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85516020808801516040808a015160608b015160808c01519251613b3e968b96909594910195865267ffffffffffffffff948516602087015292909316604085015263ffffffff908116606085015291909116608083015273ffffffffffffffffffffffffffffffffffffffff1660a082015260c00190565b604051602081830303815290604052805190602001208114613b8c576040517fd529142c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b855167ffffffffffffffff164080613cb05786516040517fe9413d3800000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063e9413d389060240160206040518083038186803b158015613c3057600080fd5b505afa158015613c44573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c6891906154fd565b905080613cb05786516040517f175dadad00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff9091166004820152602401610937565b6000886080015182604051602001613cd2929190918252602082015260400190565b6040516020818303038152906040528051906020012060001c9050613cf78982613f50565b9450505050509250925092565b60005a611388811015613d1657600080fd5b611388810390508460408204820311613d2e57600080fd5b50823b613d3a57600080fd5b60008083516020850160008789f190505b9392505050565b600080613d5d613fd9565b905060008113613d9c576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101829052602401610937565b6000815a613daa8989615bba565b613db49190615c76565b613dc686670de0b6b3a7640000615c39565b613dd09190615c39565b613dda9190615c25565b90506000613df363ffffffff871664e8d4a51000615c39565b9050613e0b816b033b2e3c9fd0803ce8000000615c76565b821115613e44576040517fe80fa38100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613e4e8183615bba565b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116331415613eda576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610937565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000613f848360000151846020015185604001518660600151868860a001518960c001518a60e001518b61010001516140ed565b60038360200151604051602001613f9c929190615aa0565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209392505050565b600b54604080517ffeaf968c0000000000000000000000000000000000000000000000000000000081529051600092670100000000000000900463ffffffff169182151591849182917f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169163feaf968c9160048083019260a0929190829003018186803b15801561407f57600080fd5b505afa158015614093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140b791906157d7565b5094509092508491505080156140db57506140d28242615c76565b8463ffffffff16105b156140e55750600a545b949350505050565b6140f6896143c4565b61415c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f7075626c6963206b6579206973206e6f74206f6e2063757276650000000000006044820152606401610937565b614165886143c4565b6141cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f67616d6d61206973206e6f74206f6e20637572766500000000000000000000006044820152606401610937565b6141d4836143c4565b61423a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f6347616d6d615769746e657373206973206e6f74206f6e2063757276650000006044820152606401610937565b614243826143c4565b6142a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f73486173685769746e657373206973206e6f74206f6e206375727665000000006044820152606401610937565b6142b5878a888761451f565b61431b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f6164647228632a706b2b732a6729213d5f755769746e657373000000000000006044820152606401610937565b60006143278a876146c2565b9050600061433a898b878b868989614726565b9050600061434b838d8d8a866148ae565b9050808a146143b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f696e76616c69642070726f6f66000000000000000000000000000000000000006044820152606401610937565b505050505050505050505050565b80516000907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f11614451576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f696e76616c696420782d6f7264696e61746500000000000000000000000000006044820152606401610937565b60208201517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f116144de576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f696e76616c696420792d6f7264696e61746500000000000000000000000000006044820152606401610937565b60208201517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f9080096145188360005b602002015161490c565b1492915050565b600073ffffffffffffffffffffffffffffffffffffffff821661459e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f626164207769746e6573730000000000000000000000000000000000000000006044820152606401610937565b6020840151600090600116156145b557601c6145b8565b601b5b905060007ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641418587600060200201510986517ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141918203925060009190890987516040805160008082526020820180845287905260ff88169282019290925260608101929092526080820183905291925060019060a0016020604051602081039080840390855afa15801561466f573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015173ffffffffffffffffffffffffffffffffffffffff9081169088161495505050505050949350505050565b6146ca61516d565b6146f7600184846040516020016146e3939291906158c2565b604051602081830303815290604052614964565b90505b614703816143c4565b612c6757805160408051602081019290925261471f91016146e3565b90506146fa565b61472e61516d565b825186517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f90819006910614156147c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f706f696e747320696e2073756d206d7573742062652064697374696e637400006044820152606401610937565b6147cc8789886149cd565b614832576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4669727374206d756c20636865636b206661696c6564000000000000000000006044820152606401610937565b61483d8486856149cd565b6148a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5365636f6e64206d756c20636865636b206661696c65640000000000000000006044820152606401610937565b613e4e868484614b5a565b6000600286868685876040516020016148cc96959493929190615850565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209695505050505050565b6000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f80848509840990507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f600782089392505050565b61496c61516d565b61497582614c89565b815261498a61498582600061450e565b614cde565b6020820181905260029006600114156149c8576020810180517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0390525b919050565b600082614a36576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f7a65726f207363616c61720000000000000000000000000000000000000000006044820152606401610937565b83516020850151600090614a4c90600290615d1b565b15614a5857601c614a5b565b601b5b905060007ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641418387096040805160008082526020820180845281905260ff86169282019290925260608101869052608081018390529192509060019060a0016020604051602081039080840390855afa158015614adb573d6000803e3d6000fd5b505050602060405103519050600086604051602001614afa919061583e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052805160209091012073ffffffffffffffffffffffffffffffffffffffff92831692169190911498975050505050505050565b614b6261516d565b835160208086015185519186015160009384938493614b8393909190614d18565b919450925090507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f858209600114614c17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f696e765a206d75737420626520696e7665727365206f66207a000000000000006044820152606401610937565b60405180604001604052807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f80614c5057614c50615d5e565b87860981526020017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8785099052979650505050505050565b805160208201205b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f81106149c857604080516020808201939093528151808203840181529082019091528051910120614c91565b6000612c67826002614d117ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f6001615bba565b901c614eae565b60008080600180827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f897ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f038808905060007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f038a0890506000614dc083838585614fa2565b9098509050614dd188828e88614ffa565b9098509050614de288828c87614ffa565b90985090506000614df58d878b85614ffa565b9098509050614e0688828686614fa2565b9098509050614e1788828e89614ffa565b9098509050818114614e9a577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f818a0998507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f82890997507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8183099650614e9e565b8196505b5050505050509450945094915050565b600080614eb961518b565b6020808252818101819052604082015260608101859052608081018490527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f60a0820152614f056151a9565b60208160c08460057ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa925082614f98576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6269674d6f64457870206661696c7572652100000000000000000000000000006044820152606401610937565b5195945050505050565b6000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8487097ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8487099097909650945050505050565b600080807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f878509905060007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f87877ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f030990507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8183087ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f86890990999098509650505050505050565b82805482825590600052602060002090810192821561513f579160200282015b8281111561513f57825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9091161782556020909201916001909101906150e5565b5061514b9291506151c7565b5090565b508054600082559060005260206000209081019061086891906151c7565b60405180604001604052806002906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5b8082111561514b57600081556001016151c8565b803573ffffffffffffffffffffffffffffffffffffffff811681146149c857600080fd5b8060408101831015612c6757600080fd5b600082601f83011261522257600080fd5b6040516040810181811067ffffffffffffffff8211171561524557615245615deb565b806040525080838560408601111561525c57600080fd5b60005b600281101561527e57813583526020928301929091019060010161525f565b509195945050505050565b600060a0828403121561529b57600080fd5b60405160a0810181811067ffffffffffffffff821117156152be576152be615deb565b6040529050806152cd83615353565b81526152db60208401615353565b60208201526152ec6040840161533f565b60408201526152fd6060840161533f565b606082015261530e608084016151dc565b60808201525092915050565b803561ffff811681146149c857600080fd5b803562ffffff811681146149c857600080fd5b803563ffffffff811681146149c857600080fd5b803567ffffffffffffffff811681146149c857600080fd5b805169ffffffffffffffffffff811681146149c857600080fd5b60006020828403121561539757600080fd5b613d4b826151dc565b600080606083850312156153b357600080fd5b6153bc836151dc565b91506153cb8460208501615200565b90509250929050565b600080600080606085870312156153ea57600080fd5b6153f3856151dc565b935060208501359250604085013567ffffffffffffffff8082111561541757600080fd5b818701915087601f83011261542b57600080fd5b81358181111561543a57600080fd5b88602082850101111561544c57600080fd5b95989497505060200194505050565b6000806040838503121561546e57600080fd5b615477836151dc565b915060208301356bffffffffffffffffffffffff8116811461549857600080fd5b809150509250929050565b6000604082840312156154b557600080fd5b613d4b8383615200565b6000604082840312156154d157600080fd5b613d4b8383615211565b6000602082840312156154ed57600080fd5b81518015158114613d4b57600080fd5b60006020828403121561550f57600080fd5b5051919050565b600080600080600060a0868803121561552e57600080fd5b8535945061553e60208701615353565b935061554c6040870161531a565b925061555a6060870161533f565b91506155686080870161533f565b90509295509295909350565b60008082840361024081121561558957600080fd5b6101a08082121561559957600080fd5b6155a1615b90565b91506155ad8686615211565b82526155bc8660408701615211565b60208301526080850135604083015260a0850135606083015260c085013560808301526155eb60e086016151dc565b60a08301526101006155ff87828801615211565b60c0840152615612876101408801615211565b60e0840152610180860135818401525081935061563186828701615289565b925050509250929050565b6000806000806000808688036101c081121561565757600080fd5b6156608861531a565b965061566e6020890161533f565b955061567c6040890161533f565b945061568a6060890161533f565b935060808801359250610120807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60830112156156c557600080fd5b6156cd615b90565b91506156db60a08a0161533f565b82526156e960c08a0161533f565b60208301526156fa60e08a0161533f565b604083015261010061570d818b0161533f565b606084015261571d828b0161533f565b608084015261572f6101408b0161532c565b60a08401526157416101608b0161532c565b60c08401526157536101808b0161532c565b60e08401526157656101a08b0161532c565b818401525050809150509295509295509295565b60006020828403121561578b57600080fd5b5035919050565b6000602082840312156157a457600080fd5b613d4b82615353565b600080604083850312156157c057600080fd5b6157c983615353565b91506153cb602084016151dc565b600080600080600060a086880312156157ef57600080fd5b6157f88661536b565b94506020860151935060408601519250606086015191506155686080870161536b565b8060005b6002811015610a5a57815184526020938401939091019060010161581f565b615848818361581b565b604001919050565b868152615860602082018761581b565b61586d606082018661581b565b61587a60a082018561581b565b61588760e082018461581b565b60609190911b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166101208201526101340195945050505050565b8381526158d2602082018461581b565b606081019190915260800192915050565b60408101612c67828461581b565b600060208083528351808285015260005b8181101561591e57858101830151858201604001528201615902565b81811115615930576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006060820161ffff86168352602063ffffffff86168185015260606040850152818551808452608086019150828701935060005b818110156159b557845183529383019391830191600101615999565b509098975050505050505050565b60006101c08201905061ffff8816825263ffffffff808816602084015280871660408401528086166060840152846080840152835481811660a0850152615a1760c08501838360201c1663ffffffff169052565b615a2e60e08501838360401c1663ffffffff169052565b615a466101008501838360601c1663ffffffff169052565b615a5e6101208501838360801c1663ffffffff169052565b62ffffff60a082901c811661014086015260b882901c811661016086015260d082901c1661018085015260e81c6101a090930192909252979650505050505050565b82815260608101613d4b602083018461581b565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015615af557845183529383019391830191600101615ad9565b5090979650505050505050565b6000608082016bffffffffffffffffffffffff87168352602067ffffffffffffffff87168185015273ffffffffffffffffffffffffffffffffffffffff80871660408601526080606086015282865180855260a087019150838801945060005b81811015615b80578551841683529484019491840191600101615b62565b50909a9950505050505050505050565b604051610120810167ffffffffffffffff81118282101715615bb457615bb4615deb565b60405290565b60008219821115615bcd57615bcd615d2f565b500190565b600067ffffffffffffffff808316818516808303821115615bf557615bf5615d2f565b01949350505050565b60006bffffffffffffffffffffffff808316818516808303821115615bf557615bf5615d2f565b600082615c3457615c34615d5e565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615615c7157615c71615d2f565b500290565b600082821015615c8857615c88615d2f565b500390565b60006bffffffffffffffffffffffff83811690831681811015615cb257615cb2615d2f565b039392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415615cec57615cec615d2f565b5060010190565b600067ffffffffffffffff80831681811415615d1157615d11615d2f565b6001019392505050565b600082615d2a57615d2a615d5e565b500690565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go b/core/internal/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go index e36c75f930d..9b22fd1c2c4 100644 --- a/core/internal/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go +++ b/core/internal/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go @@ -28,7 +28,7 @@ var ( ) var VRFExternalSubOwnerExampleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"requestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"requestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Bin: "0x60a060405234801561001057600080fd5b5060405161072038038061072083398101604081905261002f9161009e565b6001600160601b0319606083901b16608052600080546001600160a01b03199081166001600160a01b039485161790915560018054929093169181169190911790915560048054909116331790556100d1565b80516001600160a01b038116811461009957600080fd5b919050565b600080604083850312156100b157600080fd5b6100ba83610082565b91506100c860208401610082565b90509250929050565b60805160601c61062b6100f56000396000818160ed0152610155015261062b6000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c8063e89e106a11610050578063e89e106a14610094578063f2fde38b146100af578063f6eaffc8146100c257600080fd5b80631fe543e31461006c5780639561f02314610081575b600080fd5b61007f61007a36600461048c565b6100d5565b005b61007f61008f36600461057b565b610195565b61009d60035481565b60405190815260200160405180910390f35b61007f6100bd36600461041d565b610295565b61009d6100d036600461045a565b610300565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610187576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b6101918282610321565b5050565b60045473ffffffffffffffffffffffffffffffffffffffff1633146101b957600080fd5b6000546040517f5d3b1d300000000000000000000000000000000000000000000000000000000081526004810183905267ffffffffffffffff8716602482015261ffff8516604482015263ffffffff80871660648301528416608482015273ffffffffffffffffffffffffffffffffffffffff90911690635d3b1d309060a401602060405180830381600087803b15801561025357600080fd5b505af1158015610267573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028b9190610473565b6003555050505050565b60045473ffffffffffffffffffffffffffffffffffffffff1633146102b957600080fd5b600480547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6002818154811061031057600080fd5b600091825260209091200154905081565b600354821461038c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265717565737420494420697320696e636f7272656374000000000000000000604482015260640161017e565b805161039f9060029060208401906103a4565b505050565b8280548282559060005260206000209081019282156103df579160200282015b828111156103df5782518255916020019190600101906103c4565b506103eb9291506103ef565b5090565b5b808211156103eb57600081556001016103f0565b803563ffffffff8116811461041857600080fd5b919050565b60006020828403121561042f57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461045357600080fd5b9392505050565b60006020828403121561046c57600080fd5b5035919050565b60006020828403121561048557600080fd5b5051919050565b6000806040838503121561049f57600080fd5b8235915060208084013567ffffffffffffffff808211156104bf57600080fd5b818601915086601f8301126104d357600080fd5b8135818111156104e5576104e56105ef565b8060051b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f83011681018181108582111715610528576105286105ef565b604052828152858101935084860182860187018b101561054757600080fd5b600095505b8386101561056a57803585526001959095019493860193860161054c565b508096505050505050509250929050565b600080600080600060a0868803121561059357600080fd5b853567ffffffffffffffff811681146105ab57600080fd5b94506105b960208701610404565b9350604086013561ffff811681146105d057600080fd5b92506105de60608701610404565b949793965091946080013592915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go b/core/internal/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go new file mode 100644 index 00000000000..e3ecb0b3fa9 --- /dev/null +++ b/core/internal/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go @@ -0,0 +1,637 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package vrf_load_test_external_sub_owner + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +var VRFLoadTestExternalSubOwnerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"COORDINATOR\",\"outputs\":[{\"internalType\":\"contractVRFCoordinatorV2Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK\",\"outputs\":[{\"internalType\":\"contractLinkTokenInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"_requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"bytes32\",\"name\":\"_keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint16\",\"name\":\"_requestCount\",\"type\":\"uint16\"}],\"name\":\"requestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_responseCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60e060405234801561001057600080fd5b50604051610aa2380380610aa283398101604081905261002f916101ae565b6001600160601b0319606083901b1660805233806000816100975760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156100c7576100c7816100e8565b5050506001600160601b0319606092831b811660a052911b1660c0526101e1565b6001600160a01b0381163314156101415760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161008e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b03811681146101a957600080fd5b919050565b600080604083850312156101c157600080fd5b6101ca83610192565b91506101d860208401610192565b90509250929050565b60805160601c60a05160601c60c05160601c61087c610226600039600060a701526000818161010b01526101f00152600081816102b3015261031b015261087c6000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba50971461012d5780638da5cb5b14610135578063dc1670db14610153578063f2fde38b1461016a57600080fd5b8063096cb17b1461008d5780631b6b6d23146100a25780631fe543e3146100f35780633b2bcbf114610106575b600080fd5b6100a061009b36600461075a565b61017d565b005b6100c97f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100a061010136600461066b565b61029b565b6100c97f000000000000000000000000000000000000000000000000000000000000000081565b6100a061035b565b60005473ffffffffffffffffffffffffffffffffffffffff166100c9565b61015c60025481565b6040519081526020016100ea565b6100a0610178366004610615565b610458565b61018561046c565b60005b8161ffff168161ffff161015610294576040517f5d3b1d300000000000000000000000000000000000000000000000000000000081526004810184905267ffffffffffffffff8616602482015261ffff8516604482015261c3506064820152600160848201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b15801561024957600080fd5b505af115801561025d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102819190610652565b508061028c816107b6565b915050610188565b5050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461034d576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b61035782826104ef565b5050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146103dc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610344565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61046061046c565b61046981610508565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146104ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610344565b565b600280549060006104ff836107d8565b91905055505050565b73ffffffffffffffffffffffffffffffffffffffff8116331415610588576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610344565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b803561ffff8116811461061057600080fd5b919050565b60006020828403121561062757600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461064b57600080fd5b9392505050565b60006020828403121561066457600080fd5b5051919050565b6000806040838503121561067e57600080fd5b8235915060208084013567ffffffffffffffff8082111561069e57600080fd5b818601915086601f8301126106b257600080fd5b8135818111156106c4576106c4610840565b8060051b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f8301168101818110858211171561070757610707610840565b604052828152858101935084860182860187018b101561072657600080fd5b600095505b8386101561074957803585526001959095019493860193860161072b565b508096505050505050509250929050565b6000806000806080858703121561077057600080fd5b843567ffffffffffffffff8116811461078857600080fd5b9350610796602086016105fe565b9250604085013591506107ab606086016105fe565b905092959194509250565b600061ffff808316818114156107ce576107ce610811565b6001019392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561080a5761080a610811565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", +} + +var VRFLoadTestExternalSubOwnerABI = VRFLoadTestExternalSubOwnerMetaData.ABI + +var VRFLoadTestExternalSubOwnerBin = VRFLoadTestExternalSubOwnerMetaData.Bin + +func DeployVRFLoadTestExternalSubOwner(auth *bind.TransactOpts, backend bind.ContractBackend, _vrfCoordinator common.Address, _link common.Address) (common.Address, *types.Transaction, *VRFLoadTestExternalSubOwner, error) { + parsed, err := VRFLoadTestExternalSubOwnerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(VRFLoadTestExternalSubOwnerBin), backend, _vrfCoordinator, _link) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VRFLoadTestExternalSubOwner{VRFLoadTestExternalSubOwnerCaller: VRFLoadTestExternalSubOwnerCaller{contract: contract}, VRFLoadTestExternalSubOwnerTransactor: VRFLoadTestExternalSubOwnerTransactor{contract: contract}, VRFLoadTestExternalSubOwnerFilterer: VRFLoadTestExternalSubOwnerFilterer{contract: contract}}, nil +} + +type VRFLoadTestExternalSubOwner struct { + address common.Address + abi abi.ABI + VRFLoadTestExternalSubOwnerCaller + VRFLoadTestExternalSubOwnerTransactor + VRFLoadTestExternalSubOwnerFilterer +} + +type VRFLoadTestExternalSubOwnerCaller struct { + contract *bind.BoundContract +} + +type VRFLoadTestExternalSubOwnerTransactor struct { + contract *bind.BoundContract +} + +type VRFLoadTestExternalSubOwnerFilterer struct { + contract *bind.BoundContract +} + +type VRFLoadTestExternalSubOwnerSession struct { + Contract *VRFLoadTestExternalSubOwner + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type VRFLoadTestExternalSubOwnerCallerSession struct { + Contract *VRFLoadTestExternalSubOwnerCaller + CallOpts bind.CallOpts +} + +type VRFLoadTestExternalSubOwnerTransactorSession struct { + Contract *VRFLoadTestExternalSubOwnerTransactor + TransactOpts bind.TransactOpts +} + +type VRFLoadTestExternalSubOwnerRaw struct { + Contract *VRFLoadTestExternalSubOwner +} + +type VRFLoadTestExternalSubOwnerCallerRaw struct { + Contract *VRFLoadTestExternalSubOwnerCaller +} + +type VRFLoadTestExternalSubOwnerTransactorRaw struct { + Contract *VRFLoadTestExternalSubOwnerTransactor +} + +func NewVRFLoadTestExternalSubOwner(address common.Address, backend bind.ContractBackend) (*VRFLoadTestExternalSubOwner, error) { + abi, err := abi.JSON(strings.NewReader(VRFLoadTestExternalSubOwnerABI)) + if err != nil { + return nil, err + } + contract, err := bindVRFLoadTestExternalSubOwner(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwner{address: address, abi: abi, VRFLoadTestExternalSubOwnerCaller: VRFLoadTestExternalSubOwnerCaller{contract: contract}, VRFLoadTestExternalSubOwnerTransactor: VRFLoadTestExternalSubOwnerTransactor{contract: contract}, VRFLoadTestExternalSubOwnerFilterer: VRFLoadTestExternalSubOwnerFilterer{contract: contract}}, nil +} + +func NewVRFLoadTestExternalSubOwnerCaller(address common.Address, caller bind.ContractCaller) (*VRFLoadTestExternalSubOwnerCaller, error) { + contract, err := bindVRFLoadTestExternalSubOwner(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwnerCaller{contract: contract}, nil +} + +func NewVRFLoadTestExternalSubOwnerTransactor(address common.Address, transactor bind.ContractTransactor) (*VRFLoadTestExternalSubOwnerTransactor, error) { + contract, err := bindVRFLoadTestExternalSubOwner(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwnerTransactor{contract: contract}, nil +} + +func NewVRFLoadTestExternalSubOwnerFilterer(address common.Address, filterer bind.ContractFilterer) (*VRFLoadTestExternalSubOwnerFilterer, error) { + contract, err := bindVRFLoadTestExternalSubOwner(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwnerFilterer{contract: contract}, nil +} + +func bindVRFLoadTestExternalSubOwner(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(VRFLoadTestExternalSubOwnerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLoadTestExternalSubOwner.Contract.VRFLoadTestExternalSubOwnerCaller.contract.Call(opts, result, method, params...) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.VRFLoadTestExternalSubOwnerTransactor.contract.Transfer(opts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.VRFLoadTestExternalSubOwnerTransactor.contract.Transact(opts, method, params...) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLoadTestExternalSubOwner.Contract.contract.Call(opts, result, method, params...) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.contract.Transfer(opts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.contract.Transact(opts, method, params...) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCaller) COORDINATOR(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFLoadTestExternalSubOwner.contract.Call(opts, &out, "COORDINATOR") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) COORDINATOR() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.COORDINATOR(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCallerSession) COORDINATOR() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.COORDINATOR(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCaller) LINK(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFLoadTestExternalSubOwner.contract.Call(opts, &out, "LINK") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) LINK() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.LINK(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCallerSession) LINK() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.LINK(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFLoadTestExternalSubOwner.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) Owner() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.Owner(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCallerSession) Owner() (common.Address, error) { + return _VRFLoadTestExternalSubOwner.Contract.Owner(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCaller) SResponseCount(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFLoadTestExternalSubOwner.contract.Call(opts, &out, "s_responseCount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) SResponseCount() (*big.Int, error) { + return _VRFLoadTestExternalSubOwner.Contract.SResponseCount(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerCallerSession) SResponseCount() (*big.Int, error) { + return _VRFLoadTestExternalSubOwner.Contract.SResponseCount(&_VRFLoadTestExternalSubOwner.CallOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.contract.Transact(opts, "acceptOwnership") +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) AcceptOwnership() (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.AcceptOwnership(&_VRFLoadTestExternalSubOwner.TransactOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.AcceptOwnership(&_VRFLoadTestExternalSubOwner.TransactOpts) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactor) RawFulfillRandomWords(opts *bind.TransactOpts, requestId *big.Int, randomWords []*big.Int) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.contract.Transact(opts, "rawFulfillRandomWords", requestId, randomWords) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) RawFulfillRandomWords(requestId *big.Int, randomWords []*big.Int) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.RawFulfillRandomWords(&_VRFLoadTestExternalSubOwner.TransactOpts, requestId, randomWords) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorSession) RawFulfillRandomWords(requestId *big.Int, randomWords []*big.Int) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.RawFulfillRandomWords(&_VRFLoadTestExternalSubOwner.TransactOpts, requestId, randomWords) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactor) RequestRandomWords(opts *bind.TransactOpts, _subId uint64, _requestConfirmations uint16, _keyHash [32]byte, _requestCount uint16) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.contract.Transact(opts, "requestRandomWords", _subId, _requestConfirmations, _keyHash, _requestCount) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) RequestRandomWords(_subId uint64, _requestConfirmations uint16, _keyHash [32]byte, _requestCount uint16) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.RequestRandomWords(&_VRFLoadTestExternalSubOwner.TransactOpts, _subId, _requestConfirmations, _keyHash, _requestCount) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorSession) RequestRandomWords(_subId uint64, _requestConfirmations uint16, _keyHash [32]byte, _requestCount uint16) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.RequestRandomWords(&_VRFLoadTestExternalSubOwner.TransactOpts, _subId, _requestConfirmations, _keyHash, _requestCount) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.contract.Transact(opts, "transferOwnership", to) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.TransferOwnership(&_VRFLoadTestExternalSubOwner.TransactOpts, to) +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _VRFLoadTestExternalSubOwner.Contract.TransferOwnership(&_VRFLoadTestExternalSubOwner.TransactOpts, to) +} + +type VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator struct { + Event *VRFLoadTestExternalSubOwnerOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFLoadTestExternalSubOwnerOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(VRFLoadTestExternalSubOwnerOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFLoadTestExternalSubOwnerOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _VRFLoadTestExternalSubOwner.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator{contract: _VRFLoadTestExternalSubOwner.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFLoadTestExternalSubOwnerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _VRFLoadTestExternalSubOwner.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFLoadTestExternalSubOwnerOwnershipTransferRequested) + if err := _VRFLoadTestExternalSubOwner.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) ParseOwnershipTransferRequested(log types.Log) (*VRFLoadTestExternalSubOwnerOwnershipTransferRequested, error) { + event := new(VRFLoadTestExternalSubOwnerOwnershipTransferRequested) + if err := _VRFLoadTestExternalSubOwner.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFLoadTestExternalSubOwnerOwnershipTransferredIterator struct { + Event *VRFLoadTestExternalSubOwnerOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFLoadTestExternalSubOwnerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(VRFLoadTestExternalSubOwnerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *VRFLoadTestExternalSubOwnerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFLoadTestExternalSubOwnerOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFLoadTestExternalSubOwnerOwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _VRFLoadTestExternalSubOwner.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &VRFLoadTestExternalSubOwnerOwnershipTransferredIterator{contract: _VRFLoadTestExternalSubOwner.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *VRFLoadTestExternalSubOwnerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _VRFLoadTestExternalSubOwner.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFLoadTestExternalSubOwnerOwnershipTransferred) + if err := _VRFLoadTestExternalSubOwner.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwnerFilterer) ParseOwnershipTransferred(log types.Log) (*VRFLoadTestExternalSubOwnerOwnershipTransferred, error) { + event := new(VRFLoadTestExternalSubOwnerOwnershipTransferred) + if err := _VRFLoadTestExternalSubOwner.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwner) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _VRFLoadTestExternalSubOwner.abi.Events["OwnershipTransferRequested"].ID: + return _VRFLoadTestExternalSubOwner.ParseOwnershipTransferRequested(log) + case _VRFLoadTestExternalSubOwner.abi.Events["OwnershipTransferred"].ID: + return _VRFLoadTestExternalSubOwner.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (VRFLoadTestExternalSubOwnerOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (VRFLoadTestExternalSubOwnerOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (_VRFLoadTestExternalSubOwner *VRFLoadTestExternalSubOwner) Address() common.Address { + return _VRFLoadTestExternalSubOwner.address +} + +type VRFLoadTestExternalSubOwnerInterface interface { + COORDINATOR(opts *bind.CallOpts) (common.Address, error) + + LINK(opts *bind.CallOpts) (common.Address, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + SResponseCount(opts *bind.CallOpts) (*big.Int, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + RawFulfillRandomWords(opts *bind.TransactOpts, requestId *big.Int, randomWords []*big.Int) (*types.Transaction, error) + + RequestRandomWords(opts *bind.TransactOpts, _subId uint64, _requestConfirmations uint16, _keyHash [32]byte, _requestCount uint16) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFLoadTestExternalSubOwnerOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFLoadTestExternalSubOwnerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*VRFLoadTestExternalSubOwnerOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFLoadTestExternalSubOwnerOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *VRFLoadTestExternalSubOwnerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*VRFLoadTestExternalSubOwnerOwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/internal/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go b/core/internal/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go new file mode 100644 index 00000000000..2306641553e --- /dev/null +++ b/core/internal/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go @@ -0,0 +1,253 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package vrf_load_test_ownerless_consumer + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +var VRFLoadTestOwnerlessConsumerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"PRICE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"randomness\",\"type\":\"uint256\"}],\"name\":\"rawFulfillRandomness\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_responseCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60e060405234801561001057600080fd5b5060405161078d38038061078d83398101604081905261002f9161006e565b6001600160601b0319606093841b811660a0529190921b1660805260c0526100aa565b80516001600160a01b038116811461006957600080fd5b919050565b60008060006060848603121561008357600080fd5b61008c84610052565b925061009a60208501610052565b9150604084015190509250925092565b60805160601c60a05160601c60c05161068d6101006000396000818160560152818161022501528181610255015261027f01526000818160d3015261030c01526000818161018501526102d0015261068d6000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80638d859f3e1461005157806394985ddd1461008a578063a4c0ed361461009f578063dc1670db146100b2575b600080fd5b6100787f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200160405180910390f35b61009d610098366004610546565b6100bb565b005b61009d6100ad366004610462565b61016d565b61007860015481565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461015f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4f6e6c7920565246436f6f7264696e61746f722063616e2066756c66696c6c0060448201526064015b60405180910390fd5b61016982826102b3565b5050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461020c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6f6e6c792063616c6c61626c652066726f6d204c494e4b0000000000000000006044820152606401610156565b600061021a8284018461052d565b905060005b8461024a7f000000000000000000000000000000000000000000000000000000000000000083610600565b116102ab57610279827f00000000000000000000000000000000000000000000000000000000000000006102cc565b506102a47f000000000000000000000000000000000000000000000000000000000000000082610600565b905061021f565b505050505050565b600180549060006102c383610618565b91905055505050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16634000aea07f000000000000000000000000000000000000000000000000000000000000000084866000604051602001610349929190918252602082015260400190565b6040516020818303038152906040526040518463ffffffff1660e01b815260040161037693929190610568565b602060405180830381600087803b15801561039057600080fd5b505af11580156103a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103c89190610504565b5060008381526020818152604080832054815180840188905280830185905230606082015260808082018390528351808303909101815260a090910190925281519183019190912086845292909152610422906001610600565b6000858152602081815260409182902092909255805180830187905280820184905281518082038301815260609091019091528051910120949350505050565b6000806000806060858703121561047857600080fd5b843573ffffffffffffffffffffffffffffffffffffffff8116811461049c57600080fd5b935060208501359250604085013567ffffffffffffffff808211156104c057600080fd5b818701915087601f8301126104d457600080fd5b8135818111156104e357600080fd5b8860208285010111156104f557600080fd5b95989497505060200194505050565b60006020828403121561051657600080fd5b8151801515811461052657600080fd5b9392505050565b60006020828403121561053f57600080fd5b5035919050565b6000806040838503121561055957600080fd5b50508035926020909101359150565b73ffffffffffffffffffffffffffffffffffffffff8416815260006020848184015260606040840152835180606085015260005b818110156105b85785810183015185820160800152820161059c565b818111156105ca576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b6000821982111561061357610613610651565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561064a5761064a610651565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea164736f6c6343000806000a", +} + +var VRFLoadTestOwnerlessConsumerABI = VRFLoadTestOwnerlessConsumerMetaData.ABI + +var VRFLoadTestOwnerlessConsumerBin = VRFLoadTestOwnerlessConsumerMetaData.Bin + +func DeployVRFLoadTestOwnerlessConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, _vrfCoordinator common.Address, _link common.Address, _price *big.Int) (common.Address, *types.Transaction, *VRFLoadTestOwnerlessConsumer, error) { + parsed, err := VRFLoadTestOwnerlessConsumerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(VRFLoadTestOwnerlessConsumerBin), backend, _vrfCoordinator, _link, _price) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VRFLoadTestOwnerlessConsumer{VRFLoadTestOwnerlessConsumerCaller: VRFLoadTestOwnerlessConsumerCaller{contract: contract}, VRFLoadTestOwnerlessConsumerTransactor: VRFLoadTestOwnerlessConsumerTransactor{contract: contract}, VRFLoadTestOwnerlessConsumerFilterer: VRFLoadTestOwnerlessConsumerFilterer{contract: contract}}, nil +} + +type VRFLoadTestOwnerlessConsumer struct { + address common.Address + abi abi.ABI + VRFLoadTestOwnerlessConsumerCaller + VRFLoadTestOwnerlessConsumerTransactor + VRFLoadTestOwnerlessConsumerFilterer +} + +type VRFLoadTestOwnerlessConsumerCaller struct { + contract *bind.BoundContract +} + +type VRFLoadTestOwnerlessConsumerTransactor struct { + contract *bind.BoundContract +} + +type VRFLoadTestOwnerlessConsumerFilterer struct { + contract *bind.BoundContract +} + +type VRFLoadTestOwnerlessConsumerSession struct { + Contract *VRFLoadTestOwnerlessConsumer + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type VRFLoadTestOwnerlessConsumerCallerSession struct { + Contract *VRFLoadTestOwnerlessConsumerCaller + CallOpts bind.CallOpts +} + +type VRFLoadTestOwnerlessConsumerTransactorSession struct { + Contract *VRFLoadTestOwnerlessConsumerTransactor + TransactOpts bind.TransactOpts +} + +type VRFLoadTestOwnerlessConsumerRaw struct { + Contract *VRFLoadTestOwnerlessConsumer +} + +type VRFLoadTestOwnerlessConsumerCallerRaw struct { + Contract *VRFLoadTestOwnerlessConsumerCaller +} + +type VRFLoadTestOwnerlessConsumerTransactorRaw struct { + Contract *VRFLoadTestOwnerlessConsumerTransactor +} + +func NewVRFLoadTestOwnerlessConsumer(address common.Address, backend bind.ContractBackend) (*VRFLoadTestOwnerlessConsumer, error) { + abi, err := abi.JSON(strings.NewReader(VRFLoadTestOwnerlessConsumerABI)) + if err != nil { + return nil, err + } + contract, err := bindVRFLoadTestOwnerlessConsumer(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VRFLoadTestOwnerlessConsumer{address: address, abi: abi, VRFLoadTestOwnerlessConsumerCaller: VRFLoadTestOwnerlessConsumerCaller{contract: contract}, VRFLoadTestOwnerlessConsumerTransactor: VRFLoadTestOwnerlessConsumerTransactor{contract: contract}, VRFLoadTestOwnerlessConsumerFilterer: VRFLoadTestOwnerlessConsumerFilterer{contract: contract}}, nil +} + +func NewVRFLoadTestOwnerlessConsumerCaller(address common.Address, caller bind.ContractCaller) (*VRFLoadTestOwnerlessConsumerCaller, error) { + contract, err := bindVRFLoadTestOwnerlessConsumer(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VRFLoadTestOwnerlessConsumerCaller{contract: contract}, nil +} + +func NewVRFLoadTestOwnerlessConsumerTransactor(address common.Address, transactor bind.ContractTransactor) (*VRFLoadTestOwnerlessConsumerTransactor, error) { + contract, err := bindVRFLoadTestOwnerlessConsumer(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VRFLoadTestOwnerlessConsumerTransactor{contract: contract}, nil +} + +func NewVRFLoadTestOwnerlessConsumerFilterer(address common.Address, filterer bind.ContractFilterer) (*VRFLoadTestOwnerlessConsumerFilterer, error) { + contract, err := bindVRFLoadTestOwnerlessConsumer(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VRFLoadTestOwnerlessConsumerFilterer{contract: contract}, nil +} + +func bindVRFLoadTestOwnerlessConsumer(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(VRFLoadTestOwnerlessConsumerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLoadTestOwnerlessConsumer.Contract.VRFLoadTestOwnerlessConsumerCaller.contract.Call(opts, result, method, params...) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.VRFLoadTestOwnerlessConsumerTransactor.contract.Transfer(opts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.VRFLoadTestOwnerlessConsumerTransactor.contract.Transact(opts, method, params...) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFLoadTestOwnerlessConsumer.Contract.contract.Call(opts, result, method, params...) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.contract.Transfer(opts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.contract.Transact(opts, method, params...) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerCaller) PRICE(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFLoadTestOwnerlessConsumer.contract.Call(opts, &out, "PRICE") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerSession) PRICE() (*big.Int, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.PRICE(&_VRFLoadTestOwnerlessConsumer.CallOpts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerCallerSession) PRICE() (*big.Int, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.PRICE(&_VRFLoadTestOwnerlessConsumer.CallOpts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerCaller) SResponseCount(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFLoadTestOwnerlessConsumer.contract.Call(opts, &out, "s_responseCount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerSession) SResponseCount() (*big.Int, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.SResponseCount(&_VRFLoadTestOwnerlessConsumer.CallOpts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerCallerSession) SResponseCount() (*big.Int, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.SResponseCount(&_VRFLoadTestOwnerlessConsumer.CallOpts) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactor) OnTokenTransfer(opts *bind.TransactOpts, arg0 common.Address, _amount *big.Int, _data []byte) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.contract.Transact(opts, "onTokenTransfer", arg0, _amount, _data) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerSession) OnTokenTransfer(arg0 common.Address, _amount *big.Int, _data []byte) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.OnTokenTransfer(&_VRFLoadTestOwnerlessConsumer.TransactOpts, arg0, _amount, _data) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactorSession) OnTokenTransfer(arg0 common.Address, _amount *big.Int, _data []byte) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.OnTokenTransfer(&_VRFLoadTestOwnerlessConsumer.TransactOpts, arg0, _amount, _data) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactor) RawFulfillRandomness(opts *bind.TransactOpts, requestId [32]byte, randomness *big.Int) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.contract.Transact(opts, "rawFulfillRandomness", requestId, randomness) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerSession) RawFulfillRandomness(requestId [32]byte, randomness *big.Int) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.RawFulfillRandomness(&_VRFLoadTestOwnerlessConsumer.TransactOpts, requestId, randomness) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumerTransactorSession) RawFulfillRandomness(requestId [32]byte, randomness *big.Int) (*types.Transaction, error) { + return _VRFLoadTestOwnerlessConsumer.Contract.RawFulfillRandomness(&_VRFLoadTestOwnerlessConsumer.TransactOpts, requestId, randomness) +} + +func (_VRFLoadTestOwnerlessConsumer *VRFLoadTestOwnerlessConsumer) Address() common.Address { + return _VRFLoadTestOwnerlessConsumer.address +} + +type VRFLoadTestOwnerlessConsumerInterface interface { + PRICE(opts *bind.CallOpts) (*big.Int, error) + + SResponseCount(opts *bind.CallOpts) (*big.Int, error) + + OnTokenTransfer(opts *bind.TransactOpts, arg0 common.Address, _amount *big.Int, _data []byte) (*types.Transaction, error) + + RawFulfillRandomness(opts *bind.TransactOpts, requestId [32]byte, randomness *big.Int) (*types.Transaction, error) + + Address() common.Address +} diff --git a/core/internal/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go b/core/internal/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go index 83f4a121de3..bee22279358 100644 --- a/core/internal/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go +++ b/core/internal/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go @@ -28,7 +28,7 @@ var ( ) var VRFMaliciousConsumerV2MetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"setKeyHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"setKeyHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Bin: "0x60a060405234801561001057600080fd5b50604051610d9c380380610d9c83398101604081905261002f9161008e565b6001600160601b0319606083901b16608052600280546001600160a01b03199081166001600160a01b0394851617909155600380549290931691161790556100c1565b80516001600160a01b038116811461008957600080fd5b919050565b600080604083850312156100a157600080fd5b6100aa83610072565b91506100b860208401610072565b90509250929050565b60805160601c610cb66100e66000396000818161019301526101fb0152610cb66000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c8063706da1ca11610076578063e89e106a1161005b578063e89e106a14610156578063f08c5daa1461015f578063f6eaffc81461016857600080fd5b8063706da1ca146100fe578063985447101461014357600080fd5b80631fe543e3146100a857806336bfffed146100bd5780636802f726146100d0578063702a2ec9146100e3575b600080fd5b6100bb6100b63660046109d6565b61017b565b005b6100bb6100cb3660046108bc565b61023b565b6100bb6100de366004610aa4565b6103c3565b6100eb610642565b6040519081526020015b60405180910390f35b60035461012a9074010000000000000000000000000000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016100f5565b6100bb6101513660046109a4565b600555565b6100eb60015481565b6100eb60045481565b6100eb6101763660046109a4565b610731565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461022d576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b6102378282610752565b5050565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff166102c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f7375624944206e6f7420736574000000000000000000000000000000000000006044820152606401610224565b60005b815181101561023757600254600354835173ffffffffffffffffffffffffffffffffffffffff90921691637341c10c9174010000000000000000000000000000000000000000900467ffffffffffffffff169085908590811061032e5761032e610c4b565b60200260200101516040518363ffffffff1660e01b815260040161037e92919067ffffffffffffffff92909216825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b15801561039857600080fd5b505af11580156103ac573d6000803e3d6000fd5b5050505080806103bb90610beb565b9150506102c9565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff1661056e57600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a21a23e46040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561045657600080fd5b505af115801561046a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061048e9190610a7a565b600380547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000067ffffffffffffffff938416810291909117918290556002546040517f7341c10c00000000000000000000000000000000000000000000000000000000815291909204909216600483015230602483015273ffffffffffffffffffffffffffffffffffffffff1690637341c10c90604401600060405180830381600087803b15801561055557600080fd5b505af1158015610569573d6000803e3d6000fd5b505050505b6003546002546040805174010000000000000000000000000000000000000000840467ffffffffffffffff16602082015273ffffffffffffffffffffffffffffffffffffffff93841693634000aea09316918591016040516020818303038152906040526040518463ffffffff1660e01b81526004016105f093929190610ad2565b602060405180830381600087803b15801561060a57600080fd5b505af115801561061e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610237919061097b565b6002546005546003546040517f5d3b1d30000000000000000000000000000000000000000000000000000000008152600481019290925274010000000000000000000000000000000000000000900467ffffffffffffffff1660248201526001604482018190526207a1206064830152608482015260009173ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b1580156106f457600080fd5b505af1158015610708573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072c91906109bd565b905090565b6000818154811061074157600080fd5b600091825260209091200154905081565b5a600455805161076990600090602084019061085c565b5060018281556002546005546003546040517f5d3b1d30000000000000000000000000000000000000000000000000000000008152600481019290925274010000000000000000000000000000000000000000900467ffffffffffffffff1660248201526044810183905262030d406064820152608481019290925273ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b15801561081f57600080fd5b505af1158015610833573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061085791906109bd565b505050565b828054828255906000526020600020908101928215610897579160200282015b8281111561089757825182559160200191906001019061087c565b506108a39291506108a7565b5090565b5b808211156108a357600081556001016108a8565b600060208083850312156108cf57600080fd5b823567ffffffffffffffff8111156108e657600080fd5b8301601f810185136108f757600080fd5b803561090a61090582610bc7565b610b78565b80828252848201915084840188868560051b870101111561092a57600080fd5b60009450845b8481101561096d57813573ffffffffffffffffffffffffffffffffffffffff8116811461095b578687fd5b84529286019290860190600101610930565b509098975050505050505050565b60006020828403121561098d57600080fd5b8151801515811461099d57600080fd5b9392505050565b6000602082840312156109b657600080fd5b5035919050565b6000602082840312156109cf57600080fd5b5051919050565b600080604083850312156109e957600080fd5b8235915060208084013567ffffffffffffffff811115610a0857600080fd5b8401601f81018613610a1957600080fd5b8035610a2761090582610bc7565b80828252848201915084840189868560051b8701011115610a4757600080fd5b600094505b83851015610a6a578035835260019490940193918501918501610a4c565b5080955050505050509250929050565b600060208284031215610a8c57600080fd5b815167ffffffffffffffff8116811461099d57600080fd5b600060208284031215610ab657600080fd5b81356bffffffffffffffffffffffff8116811461099d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84168152600060206bffffffffffffffffffffffff85168184015260606040840152835180606085015260005b81811015610b3057858101830151858201608001528201610b14565b81811115610b42576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610bbf57610bbf610c7a565b604052919050565b600067ffffffffffffffff821115610be157610be1610c7a565b5060051b60200190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610c44577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go b/core/internal/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go index 0c7415bb138..b767d7984e8 100644 --- a/core/internal/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go +++ b/core/internal/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go @@ -28,7 +28,7 @@ var ( ) var VRFOwnerlessConsumerExampleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"randomness\",\"type\":\"uint256\"}],\"name\":\"rawFulfillRandomness\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_randomnessOutput\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"OnlyCallableFromLink\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"randomness\",\"type\":\"uint256\"}],\"name\":\"rawFulfillRandomness\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_randomnessOutput\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", Bin: "0x60c060405234801561001057600080fd5b506040516106a73803806106a783398101604081905261002f91610069565b6001600160601b0319606092831b811660a052911b1660805261009c565b80516001600160a01b038116811461006457600080fd5b919050565b6000806040838503121561007c57600080fd5b6100858361004d565b91506100936020840161004d565b90509250929050565b60805160601c60a05160601c6105d36100d46000396000818160b50152610293015260008181610167015261025701526105d36000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80635eb797831461005157806394985ddd1461006c578063a4c0ed3614610081578063e89e106a14610094575b600080fd5b61005a60015481565b60405190815260200160405180910390f35b61007f61007a3660046104cd565b61009d565b005b61007f61008f3660046103e9565b61014f565b61005a60025481565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610141576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4f6e6c7920565246436f6f7264696e61746f722063616e2066756c66696c6c0060448201526064015b60405180910390fd5b61014b82826101e2565b5050565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146101be576040517f44b0e3c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006101cc828401846104b4565b90506101d88185610253565b6002555050505050565b600254821461024d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265717565737420494420697320696e636f72726563740000000000000000006044820152606401610138565b60015550565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16634000aea07f0000000000000000000000000000000000000000000000000000000000000000848660006040516020016102d0929190918252602082015260400190565b6040516020818303038152906040526040518463ffffffff1660e01b81526004016102fd939291906104ef565b602060405180830381600087803b15801561031757600080fd5b505af115801561032b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061034f919061048b565b5060008381526020818152604080832054815180840188905280830185905230606082015260808082018390528351808303909101815260a0909101909252815191830191909120868452929091526103a9906001610587565b6000858152602081815260409182902092909255805180830187905280820184905281518082038301815260609091019091528051910120949350505050565b600080600080606085870312156103ff57600080fd5b843573ffffffffffffffffffffffffffffffffffffffff8116811461042357600080fd5b935060208501359250604085013567ffffffffffffffff8082111561044757600080fd5b818701915087601f83011261045b57600080fd5b81358181111561046a57600080fd5b88602082850101111561047c57600080fd5b95989497505060200194505050565b60006020828403121561049d57600080fd5b815180151581146104ad57600080fd5b9392505050565b6000602082840312156104c657600080fd5b5035919050565b600080604083850312156104e057600080fd5b50508035926020909101359150565b73ffffffffffffffffffffffffffffffffffffffff8416815260006020848184015260606040840152835180606085015260005b8181101561053f57858101830151858201608001528201610523565b81811115610551576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b600082198211156105c1577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50019056fea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go b/core/internal/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go index a4ffb69b08f..82d9b3b3702 100644 --- a/core/internal/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go +++ b/core/internal/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go @@ -28,7 +28,7 @@ var ( ) var VRFSingleConsumerExampleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"fundAndRequestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"requestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestConfig\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"subscribe\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"unsubscribe\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"fundAndRequestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"requestRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestConfig\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"subscribe\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"unsubscribe\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Bin: "0x60a06040523480156200001157600080fd5b50604051620013383803806200133883398101604081905262000034916200031a565b606086811b6001600160601b0319166080908152600080546001600160a01b03808b166001600160a01b0319928316178355600180548316918b16919091179055600680543392169190911790556040805160a08101825291825263ffffffff8881166020840181905261ffff891692840183905290871694830185905291909201849052600280546001600160701b0319166801000000000000000090920261ffff60601b1916919091176c010000000000000000000000009092029190911763ffffffff60701b1916600160701b90920291909117905560038190556200011c62000128565b505050505050620003e2565b6006546001600160a01b031633146200014057600080fd5b604080516001808252818301909252600091602080830190803683370190505090503081600081518110620001795762000179620003cc565b60200260200101906001600160a01b031690816001600160a01b03168152505060008054906101000a90046001600160a01b03166001600160a01b031663a21a23e46040518163ffffffff1660e01b8152600401602060405180830381600087803b158015620001e857600080fd5b505af1158015620001fd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022391906200039a565b600280546001600160401b0319166001600160401b039290921691821790556000805483516001600160a01b0390911692637341c10c9290918591906200026e576200026e620003cc565b60200260200101516040518363ffffffff1660e01b8152600401620002b19291906001600160401b039290921682526001600160a01b0316602082015260400190565b600060405180830381600087803b158015620002cc57600080fd5b505af1158015620002e1573d6000803e3d6000fd5b5050505050565b80516001600160a01b03811681146200030057600080fd5b919050565b805163ffffffff811681146200030057600080fd5b60008060008060008060c087890312156200033457600080fd5b6200033f87620002e8565b95506200034f60208801620002e8565b94506200035f6040880162000305565b9350606087015161ffff811681146200037757600080fd5b9250620003876080880162000305565b915060a087015190509295509295509295565b600060208284031215620003ad57600080fd5b81516001600160401b0381168114620003c557600080fd5b9392505050565b634e487b7160e01b600052603260045260246000fd5b60805160601c610f3062000408600039600081816102ea01526103520152610f306000f3fe608060405234801561001057600080fd5b50600436106100bd5760003560e01c806386850e9311610076578063e0c862891161005b578063e0c86289146101cb578063e89e106a146101d3578063f6eaffc8146101ea57600080fd5b806386850e93146101b05780638f449a05146101c357600080fd5b80636fd700bb116100a75780636fd700bb146100ea5780637262561c146100fd5780637db9263f1461011057600080fd5b8062f714ce146100c25780631fe543e3146100d7575b600080fd5b6100d56100d0366004610ce8565b6101fd565b005b6100d56100e5366004610d14565b6102d2565b6100d56100f8366004610cb6565b610392565b6100d561010b366004610c72565b6105e0565b6002546003546101699167ffffffffffffffff81169163ffffffff68010000000000000000830481169261ffff6c01000000000000000000000000820416926e0100000000000000000000000000009091049091169085565b6040805167ffffffffffffffff909616865263ffffffff948516602087015261ffff90931692850192909252919091166060830152608082015260a0015b60405180910390f35b6100d56101be366004610cb6565b6106c8565b6100d56107ad565b6100d56109d4565b6101dc60055481565b6040519081526020016101a7565b6101dc6101f8366004610cb6565b610b35565b60065473ffffffffffffffffffffffffffffffffffffffff16331461022157600080fd5b6001546040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018590529091169063a9059cbb90604401602060405180830381600087803b15801561029557600080fd5b505af11580156102a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102cd9190610c94565b505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610384576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b61038e8282610b56565b5050565b60065473ffffffffffffffffffffffffffffffffffffffff1633146103b657600080fd5b6040805160a08101825260025467ffffffffffffffff811680835263ffffffff680100000000000000008304811660208086019190915261ffff6c01000000000000000000000000850416858701526e010000000000000000000000000000909304166060840152600354608084015260015460005485518085019390935285518084039094018452828601958690527f4000aea000000000000000000000000000000000000000000000000000000000909552929373ffffffffffffffffffffffffffffffffffffffff93841693634000aea09361049e9391909216918791604401610e2d565b602060405180830381600087803b1580156104b857600080fd5b505af11580156104cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104f09190610c94565b50600054608082015182516040808501516020860151606087015192517f5d3b1d30000000000000000000000000000000000000000000000000000000008152600481019590955267ffffffffffffffff909316602485015261ffff16604484015263ffffffff918216606484015216608482015273ffffffffffffffffffffffffffffffffffffffff90911690635d3b1d309060a401602060405180830381600087803b1580156105a157600080fd5b505af11580156105b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d99190610ccf565b6005555050565b60065473ffffffffffffffffffffffffffffffffffffffff16331461060457600080fd5b6000546002546040517fd7ae1d3000000000000000000000000000000000000000000000000000000000815267ffffffffffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301529091169063d7ae1d3090604401600060405180830381600087803b15801561068557600080fd5b505af1158015610699573d6000803e3d6000fd5b5050600280547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000169055505050565b60065473ffffffffffffffffffffffffffffffffffffffff1633146106ec57600080fd5b6001546000546002546040805167ffffffffffffffff909216602083015273ffffffffffffffffffffffffffffffffffffffff93841693634000aea09316918591016040516020818303038152906040526040518463ffffffff1660e01b815260040161075b93929190610e2d565b602060405180830381600087803b15801561077557600080fd5b505af1158015610789573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061038e9190610c94565b60065473ffffffffffffffffffffffffffffffffffffffff1633146107d157600080fd5b60408051600180825281830190925260009160208083019080368337019050509050308160008151811061080757610807610ec5565b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a21a23e46040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156108a957600080fd5b505af11580156108bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e19190610e03565b600280547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff92909216918217905560008054835173ffffffffffffffffffffffffffffffffffffffff90911692637341c10c92909185919061094f5761094f610ec5565b60200260200101516040518363ffffffff1660e01b815260040161099f92919067ffffffffffffffff92909216825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b1580156109b957600080fd5b505af11580156109cd573d6000803e3d6000fd5b5050505050565b60065473ffffffffffffffffffffffffffffffffffffffff1633146109f857600080fd5b6040805160a08101825260025467ffffffffffffffff811680835263ffffffff68010000000000000000830481166020850181905261ffff6c010000000000000000000000008504168587018190526e010000000000000000000000000000909404909116606085018190526003546080860181905260005496517f5d3b1d3000000000000000000000000000000000000000000000000000000000815260048101919091526024810193909352604483019390935260648201526084810191909152909173ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b158015610af757600080fd5b505af1158015610b0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2f9190610ccf565b60055550565b60048181548110610b4557600080fd5b600091825260209091200154905081565b6005548214610bc1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265717565737420494420697320696e636f7272656374000000000000000000604482015260640161037b565b80516004805482825560008290526102cd927f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b91820191602086018215610c24579160200282015b82811115610c24578251825591602001919060010190610c09565b50610c30929150610c34565b5090565b5b80821115610c305760008155600101610c35565b803573ffffffffffffffffffffffffffffffffffffffff81168114610c6d57600080fd5b919050565b600060208284031215610c8457600080fd5b610c8d82610c49565b9392505050565b600060208284031215610ca657600080fd5b81518015158114610c8d57600080fd5b600060208284031215610cc857600080fd5b5035919050565b600060208284031215610ce157600080fd5b5051919050565b60008060408385031215610cfb57600080fd5b82359150610d0b60208401610c49565b90509250929050565b60008060408385031215610d2757600080fd5b8235915060208084013567ffffffffffffffff80821115610d4757600080fd5b818601915086601f830112610d5b57600080fd5b813581811115610d6d57610d6d610ef4565b8060051b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f83011681018181108582111715610db057610db0610ef4565b604052828152858101935084860182860187018b1015610dcf57600080fd5b600095505b83861015610df2578035855260019590950194938601938601610dd4565b508096505050505050509250929050565b600060208284031215610e1557600080fd5b815167ffffffffffffffff81168114610c8d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8416815260006020848184015260606040840152835180606085015260005b81811015610e7d57858101830151858201608001528201610e61565b81811115610e8f576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go b/core/internal/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go index f6e985cbb00..da46eb0e505 100644 --- a/core/internal/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go +++ b/core/internal/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go @@ -28,7 +28,7 @@ var ( ) var VRFV2RevertingExampleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"minReqConfs\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_gasAvailable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_randomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_requestId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_subId\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"testCreateSubscriptionAndFund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"subId\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"minReqConfs\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"topUpSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"name\":\"updateSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Bin: "0x60a060405234801561001057600080fd5b50604051610da6380380610da683398101604081905261002f9161008e565b6001600160601b0319606083901b16608052600280546001600160a01b03199081166001600160a01b0394851617909155600380549290931691161790556100c1565b80516001600160a01b038116811461008957600080fd5b919050565b600080604083850312156100a157600080fd5b6100aa83610072565b91506100b860208401610072565b90509250929050565b60805160601c610cc06100e66000396000818161019e01526102060152610cc06000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c80636802f72611610076578063e89e106a1161005b578063e89e106a14610161578063f08c5daa1461016a578063f6eaffc81461017357600080fd5b80636802f72614610109578063706da1ca1461011c57600080fd5b80631fe543e3146100a857806327784fad146100bd5780632fa4e442146100e357806336bfffed146100f6575b600080fd5b6100bb6100b63660046109d4565b610186565b005b6100d06100cb366004610939565b610246565b6040519081526020015b60405180910390f35b6100bb6100f1366004610a95565b610328565b6100bb610104366004610851565b610488565b6100bb610117366004610a95565b610610565b6003546101489074010000000000000000000000000000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016100da565b6100d060015481565b6100d060045481565b6100d06101813660046109a2565b610817565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610238576040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660248201526044015b60405180910390fd5b6102428282600080fd5b5050565b6002546040517f5d3b1d300000000000000000000000000000000000000000000000000000000081526004810187905267ffffffffffffffff8616602482015261ffff8516604482015263ffffffff80851660648301528316608482015260009173ffffffffffffffffffffffffffffffffffffffff1690635d3b1d309060a401602060405180830381600087803b1580156102e157600080fd5b505af11580156102f5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061031991906109bb565b60018190559695505050505050565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff166103b3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f737562206e6f7420736574000000000000000000000000000000000000000000604482015260640161022f565b6003546002546040805174010000000000000000000000000000000000000000840467ffffffffffffffff16602082015273ffffffffffffffffffffffffffffffffffffffff93841693634000aea09316918591015b6040516020818303038152906040526040518463ffffffff1660e01b815260040161043693929190610ac3565b602060405180830381600087803b15801561045057600080fd5b505af1158015610464573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102429190610910565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff16610513576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f7375624944206e6f742073657400000000000000000000000000000000000000604482015260640161022f565b60005b815181101561024257600254600354835173ffffffffffffffffffffffffffffffffffffffff90921691637341c10c9174010000000000000000000000000000000000000000900467ffffffffffffffff169085908590811061057b5761057b610c3c565b60200260200101516040518363ffffffff1660e01b81526004016105cb92919067ffffffffffffffff92909216825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b1580156105e557600080fd5b505af11580156105f9573d6000803e3d6000fd5b50505050808061060890610bdc565b915050610516565b60035474010000000000000000000000000000000000000000900467ffffffffffffffff166103b357600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a21a23e46040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156106a357600080fd5b505af11580156106b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106db9190610a78565b600380547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000067ffffffffffffffff938416810291909117918290556002546040517f7341c10c00000000000000000000000000000000000000000000000000000000815291909204909216600483015230602483015273ffffffffffffffffffffffffffffffffffffffff1690637341c10c90604401600060405180830381600087803b1580156107a257600080fd5b505af11580156107b6573d6000803e3d6000fd5b50506003546002546040805174010000000000000000000000000000000000000000840467ffffffffffffffff16602082015273ffffffffffffffffffffffffffffffffffffffff9384169550634000aea094509290911691859101610409565b6000818154811061082757600080fd5b600091825260209091200154905081565b803563ffffffff8116811461084c57600080fd5b919050565b6000602080838503121561086457600080fd5b823567ffffffffffffffff81111561087b57600080fd5b8301601f8101851361088c57600080fd5b803561089f61089a82610bb8565b610b69565b80828252848201915084840188868560051b87010111156108bf57600080fd5b60009450845b8481101561090257813573ffffffffffffffffffffffffffffffffffffffff811681146108f0578687fd5b845292860192908601906001016108c5565b509098975050505050505050565b60006020828403121561092257600080fd5b8151801515811461093257600080fd5b9392505050565b600080600080600060a0868803121561095157600080fd5b85359450602086013561096381610c9a565b9350604086013561ffff8116811461097a57600080fd5b925061098860608701610838565b915061099660808701610838565b90509295509295909350565b6000602082840312156109b457600080fd5b5035919050565b6000602082840312156109cd57600080fd5b5051919050565b600080604083850312156109e757600080fd5b8235915060208084013567ffffffffffffffff811115610a0657600080fd5b8401601f81018613610a1757600080fd5b8035610a2561089a82610bb8565b80828252848201915084840189868560051b8701011115610a4557600080fd5b600094505b83851015610a68578035835260019490940193918501918501610a4a565b5080955050505050509250929050565b600060208284031215610a8a57600080fd5b815161093281610c9a565b600060208284031215610aa757600080fd5b81356bffffffffffffffffffffffff8116811461093257600080fd5b73ffffffffffffffffffffffffffffffffffffffff84168152600060206bffffffffffffffffffffffff85168184015260606040840152835180606085015260005b81811015610b2157858101830151858201608001528201610b05565b81811115610b33576000608083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160800195945050505050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610bb057610bb0610c6b565b604052919050565b600067ffffffffffffffff821115610bd257610bd2610c6b565b5060051b60200190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610c35577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81168114610cb057600080fd5b5056fea164736f6c6343000806000a", } diff --git a/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 51e85104a38..01491dc3eb1 100644 --- a/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,29 +1,33 @@ GETH_VERSION: 1.10.16 -aggregator_v2v3_interface: ../../../contracts/solc/v0.8/AggregatorV2V3Interface.abi ../../../contracts/solc/v0.8/AggregatorV2V3Interface.bin ec03a11bfb3d34b3e9a8d25ad58751aa3e3e160144f1979d2b023a0e4582b964 +aggregator_v2v3_interface: ../../../contracts/solc/v0.8/AggregatorV2V3Interface.abi ../../../contracts/solc/v0.8/AggregatorV2V3Interface.bin 95e8814b408bb05bf21742ef580d98698b7db6a9bac6a35c3de12b23aec4ee28 aggregator_v3_interface: ../../../contracts/solc/v0.8/AggregatorV3Interface.abi ../../../contracts/solc/v0.8/AggregatorV3Interface.bin 351b55d3b0f04af67db6dfb5c92f1c64479400ca1fec77afc20bc0ce65cb49ab +batch_blockhash_store: ../../../contracts/solc/v0.8/BatchBlockhashStore.abi ../../../contracts/solc/v0.8/BatchBlockhashStore.bin 9220f1ed6c576863a2f5b34263846660e742f3513c19eb9e2562a374cb70c252 blockhash_store: ../../../contracts/solc/v0.6/BlockhashStore.abi ../../../contracts/solc/v0.6/BlockhashStore.bin 6b3da771f033b3a5e53bf112396d0698debf65a8dcd81370f07d72948c8b14d9 consumer_wrapper: ../../../contracts/solc/v0.7/Consumer.abi ../../../contracts/solc/v0.7/Consumer.bin 894d1cbd920dccbd36d92918c1037c6ded34f66f417ccb18ec3f33c64ef83ec5 -derived_price_feed_wrapper: ../../../contracts/solc/v0.7/DerivedPriceFeed.abi ../../../contracts/solc/v0.7/DerivedPriceFeed.bin 754190a4e868d35913f7f4ab3fbc605afdbc1b64d68ea53d8de89fd6b036fe06 +derived_price_feed_wrapper: ../../../contracts/solc/v0.8/DerivedPriceFeed.abi ../../../contracts/solc/v0.8/DerivedPriceFeed.bin c8542e6c850c2d0fffb79a7f7213dc927ec64e6ddd54e1224cb2fb4a13aabdd0 flags_wrapper: ../../../contracts/solc/v0.6/Flags.abi ../../../contracts/solc/v0.6/Flags.bin 2034d1b562ca37a63068851915e3703980276e8d5f7db6db8a3351a49d69fc4a flux_aggregator_wrapper: ../../../contracts/solc/v0.6/FluxAggregator.abi ../../../contracts/solc/v0.6/FluxAggregator.bin a3b0a6396c4aa3b5ee39b3c4bd45efc89789d4859379a8a92caca3a0496c5794 +keeper_registry_vb_wrapper: ../../../contracts/solc/v0.7/KeeperRegistryVB.abi ../../../contracts/solc/v0.7/KeeperRegistryVB.bin af13a53e7350624fab49e259ca484098361d7b72eb8f4e4338b659d766e5bee5 keeper_registry_wrapper: ../../../contracts/solc/v0.7/KeeperRegistry.abi ../../../contracts/solc/v0.7/KeeperRegistry.bin f5c76c6fc35e775da0a4737beea8ebcb6f37ca78db3c21811f17b6f3d5623379 multiwordconsumer_wrapper: ../../../contracts/solc/v0.7/MultiWordConsumer.abi ../../../contracts/solc/v0.7/MultiWordConsumer.bin 6e68abdf614e3ed0f5066c1b5f9d7c1199f1e7c5c5251fe8a471344a59afc6ba offchain_aggregator_wrapper: OffchainAggregator/OffchainAggregator.abi - 5f97dc197fd4e2b999856b9b3fa7c2aaf0c700c71d7009d7d017d233bc855877 operator_wrapper: ../../../contracts/solc/v0.7/Operator.abi ../../../contracts/solc/v0.7/Operator.bin 08965dbb26f62739c1ce4d0941f30c0dd08003648823e7d722900b48270ffc2b oracle_wrapper: ../../../contracts/solc/v0.6/Oracle.abi ../../../contracts/solc/v0.6/Oracle.bin 7af2fbac22a6e8c2847e8e685a5400cac5101d72ddf5365213beb79e4dede43a solidity_vrf_consumer_interface: ../../../contracts/solc/v0.6/VRFConsumer.abi ../../../contracts/solc/v0.6/VRFConsumer.bin a79da241ca9525f6a96adc5f55651b1f91e1579b85fec7b02a51d06bd6018ee3 -solidity_vrf_consumer_interface_v08: ../../../contracts/solc/v0.8/VRFConsumer.abi ../../../contracts/solc/v0.8/VRFConsumer.bin 38ed0884fe7023cae96077a7881efd7fe236cc471db6ee1fe3934c68a9b7ab9a +solidity_vrf_consumer_interface_v08: ../../../contracts/solc/v0.8/VRFConsumer.abi ../../../contracts/solc/v0.8/VRFConsumer.bin 93311eeed53bc3127cfe46f906614a58dde3a95ec87b8c1caaa1be234384fefe solidity_vrf_coordinator_interface: ../../../contracts/solc/v0.6/VRFCoordinator.abi ../../../contracts/solc/v0.6/VRFCoordinator.bin a23d3c395156804788c7f6fbda2994e8f7184304c0f0c9f2c4ddeaf073d346d2 solidity_vrf_request_id: ../../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper.abi ../../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper.bin 383b59e861732c1911ddb7b002c6158608496ce889979296527215fd0366b318 -solidity_vrf_request_id_v08: ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.abi ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.bin cae4c8e73f9d64886b0bdc02c40acd8b252b70e9499ce248b2ed0f480ccfda73 -solidity_vrf_v08_verifier_wrapper: ../../../contracts/solc/v0.8/VRFTestHelper.abi ../../../contracts/solc/v0.8/VRFTestHelper.bin c9f113405fc2e57371721c34f112aa3038b18facc25d8124ddeecfa573ea7943 +solidity_vrf_request_id_v08: ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.abi ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.bin f2559015d6f3e5d285c57b011be9b2300632e93dd6c4524e58202d6200f09edc +solidity_vrf_v08_verifier_wrapper: ../../../contracts/solc/v0.8/VRFTestHelper.abi ../../../contracts/solc/v0.8/VRFTestHelper.bin f37f8b21a81c113085c6137835a2246db6ebda07da455c4f2b5c7ec60c725c3b solidity_vrf_verifier_wrapper: ../../../contracts/solc/v0.6/VRFTestHelper.abi ../../../contracts/solc/v0.6/VRFTestHelper.bin 44c2b67d8d2990ab580453deb29d63508c6147a3dc49908a1db563bef06e6474 -upkeep_counter_wrapper: ../../../contracts/solc/v0.7/UpkeepCounter.abi ../../../contracts/solc/v0.7/UpkeepCounter.bin 402754b33702d257d00c17a4579ad6dbf15bc6f25648e7a26ce8e09b45b30a93 +upkeep_counter_wrapper: ../../../contracts/solc/v0.7/UpkeepCounter.abi ../../../contracts/solc/v0.7/UpkeepCounter.bin 901961ebf18906febc1c350f02da85c7ea1c2a68da70cfd94efa27c837a48663 upkeep_perform_counter_restrictive_wrapper: ../../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive.abi ../../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive.bin 0e9c8a89d38491ca7f65e75201a8666c5e548846a1cd85a209e580dad06db386 -vrf_consumer_v2: ../../../contracts/solc/v0.8/VRFConsumerV2.abi ../../../contracts/solc/v0.8/VRFConsumerV2.bin 268d82a8d816585f8ed870900617bcb9364998d4f0c9620174a2eb0882a10015 -vrf_coordinator_v2: ../../../contracts/solc/v0.8/VRFCoordinatorV2.abi ../../../contracts/solc/v0.8/VRFCoordinatorV2.bin cafe44779884947d02bf511a54aa85c8f87196fc9ebf1a24d1efb97af63fc2a1 -vrf_external_sub_owner_example: ../../../contracts/solc/v0.8/VRFExternalSubOwnerExample.abi ../../../contracts/solc/v0.8/VRFExternalSubOwnerExample.bin 506fab6f3ac2111353d2a3570699b143b4e0454fb0499fce2eb0769680abd376 -vrf_malicious_consumer_v2: ../../../contracts/solc/v0.8/VRFMaliciousConsumerV2.abi ../../../contracts/solc/v0.8/VRFMaliciousConsumerV2.bin b9fde692c1ebd6d029a21d79da528aa39f92f67ad51aa5c18f4c0605469f287b -vrf_ownerless_consumer_example: ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.abi ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.bin c77059861450e00686fc8ba7721416a8a4c30a152ea13a8aef1705d2db7c4678 -vrf_single_consumer_example: ../../../contracts/solc/v0.8/VRFSingleConsumerExample.abi ../../../contracts/solc/v0.8/VRFSingleConsumerExample.bin f2be46cdb725e10113722b781102c3431a4a51f35977ce0d6ee4eb69ea948d1d -vrfv2_reverting_example: ../../../contracts/solc/v0.8/VRFV2RevertingExample.abi ../../../contracts/solc/v0.8/VRFV2RevertingExample.bin a7f0752063b7c4e2b6880b4b39ec3afe1a58d3a4cc656fab812613d9d5e6847a +vrf_consumer_v2: ../../../contracts/solc/v0.8/VRFConsumerV2.abi ../../../contracts/solc/v0.8/VRFConsumerV2.bin ad31c7a9e17e0e5d2e334f6478b7a2435a17c12f9fc33143729d7452bfd0cd91 +vrf_coordinator_v2: ../../../contracts/solc/v0.8/VRFCoordinatorV2.abi ../../../contracts/solc/v0.8/VRFCoordinatorV2.bin 7839d54d662197ad8f987495b6461e8ea9c66746b79a744bfd74ff086c276be0 +vrf_external_sub_owner_example: ../../../contracts/solc/v0.8/VRFExternalSubOwnerExample.abi ../../../contracts/solc/v0.8/VRFExternalSubOwnerExample.bin 14f888eb313930b50233a6f01ea31eba0206b7f41a41f6311670da8bb8a26963 +vrf_load_test_external_sub_owner: ../../../contracts/solc/v0.8/VRFLoadTestExternalSubOwner.abi ../../../contracts/solc/v0.8/VRFLoadTestExternalSubOwner.bin 2097faa70265e420036cc8a3efb1f1e0836ad2d7323b295b9a26a125dbbe6c7d +vrf_load_test_ownerless_consumer: ../../../contracts/solc/v0.8/VRFLoadTestOwnerlessConsumer.abi ../../../contracts/solc/v0.8/VRFLoadTestOwnerlessConsumer.bin 74f914843cbc70b9c3079c3e1c709382ce415225e8bb40113e7ac018bfcb0f5c +vrf_malicious_consumer_v2: ../../../contracts/solc/v0.8/VRFMaliciousConsumerV2.abi ../../../contracts/solc/v0.8/VRFMaliciousConsumerV2.bin 68ecbaa5dfa616ed5d628e57772fa17569719fc0b0a2a10bcd27ba01c49bc998 +vrf_ownerless_consumer_example: ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.abi ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.bin 9893b3805863273917fb282eed32274e32aa3d5c2a67a911510133e1218132be +vrf_single_consumer_example: ../../../contracts/solc/v0.8/VRFSingleConsumerExample.abi ../../../contracts/solc/v0.8/VRFSingleConsumerExample.bin 892a5ed35da2e933f7fd7835cd6f7f70ef3aa63a9c03a22c5b1fd026711b0ece +vrfv2_reverting_example: ../../../contracts/solc/v0.8/VRFV2RevertingExample.abi ../../../contracts/solc/v0.8/VRFV2RevertingExample.bin 0e4eb8e0f92fab1f74a6bef7951b511e2c2f7b7134ca1dc65928234ec73dca9a diff --git a/core/internal/gethwrappers/go_generate.go b/core/internal/gethwrappers/go_generate.go index 7991ed74335..ba569442fcf 100644 --- a/core/internal/gethwrappers/go_generate.go +++ b/core/internal/gethwrappers/go_generate.go @@ -16,18 +16,20 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/Consumer.abi ../../../contracts/solc/v0.7/Consumer.bin Consumer consumer_wrapper //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/MultiWordConsumer.abi ../../../contracts/solc/v0.7/MultiWordConsumer.bin MultiWordConsumer multiwordconsumer_wrapper //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/Operator.abi ../../../contracts/solc/v0.7/Operator.bin Operator operator_wrapper +//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/BatchBlockhashStore.abi ../../../contracts/solc/v0.8/BatchBlockhashStore.bin BatchBlockhashStore batch_blockhash_store //go:generate go run ./generation/generate/wrap.go OffchainAggregator/OffchainAggregator.abi - OffchainAggregator offchain_aggregator_wrapper //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/KeeperRegistry.abi ../../../contracts/solc/v0.7/KeeperRegistry.bin KeeperRegistry keeper_registry_wrapper +//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/KeeperRegistryVB.abi ../../../contracts/solc/v0.7/KeeperRegistryVB.bin KeeperRegistryVB keeper_registry_vb_wrapper //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive.abi ../../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive.bin UpkeepPerformCounterRestrictive upkeep_perform_counter_restrictive_wrapper //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/UpkeepCounter.abi ../../../contracts/solc/v0.7/UpkeepCounter.bin UpkeepCounter upkeep_counter_wrapper -//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.7/DerivedPriceFeed.abi ../../../contracts/solc/v0.7/DerivedPriceFeed.bin DerivedPriceFeed derived_price_feed_wrapper - // v0.8 VRFConsumer //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/VRFConsumer.abi ../../../contracts/solc/v0.8/VRFConsumer.bin VRFConsumer solidity_vrf_consumer_interface_v08 //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.abi ../../../contracts/solc/v0.8/VRFRequestIDBaseTestHelper.bin VRFRequestIDBaseTestHelper solidity_vrf_request_id_v08 //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.abi ../../../contracts/solc/v0.8/VRFOwnerlessConsumerExample.bin VRFOwnerlessConsumerExample vrf_ownerless_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/VRFLoadTestOwnerlessConsumer.abi ../../../contracts/solc/v0.8/VRFLoadTestOwnerlessConsumer.bin VRFLoadTestOwnerlessConsumer vrf_load_test_ownerless_consumer +//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/VRFLoadTestExternalSubOwner.abi ../../../contracts/solc/v0.8/VRFLoadTestExternalSubOwner.bin VRFLoadTestExternalSubOwner vrf_load_test_external_sub_owner //go:generate mockery --recursive --name FluxAggregatorInterface --output ../mocks/ --case=underscore --structname FluxAggregator --filename flux_aggregator.go //go:generate mockery --recursive --name FlagsInterface --output ../mocks/ --case=underscore --structname Flags --filename flags.go @@ -46,6 +48,7 @@ package gethwrappers // Aggregators //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/AggregatorV2V3Interface.abi ../../../contracts/solc/v0.8/AggregatorV2V3Interface.bin AggregatorV2V3Interface aggregator_v2v3_interface //go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/AggregatorV3Interface.abi ../../../contracts/solc/v0.8/AggregatorV3Interface.bin AggregatorV3Interface aggregator_v3_interface +//go:generate go run ./generation/generate/wrap.go ../../../contracts/solc/v0.8/DerivedPriceFeed.abi ../../../contracts/solc/v0.8/DerivedPriceFeed.bin DerivedPriceFeed derived_price_feed_wrapper // To run these commands, you must either install docker, or the correct version // of abigen. The latter can be installed with these commands, at least on linux: diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go index 54dff500b5a..8aeb113c8d5 100644 --- a/core/internal/mocks/application.go +++ b/core/internal/mocks/application.go @@ -6,8 +6,6 @@ import ( big "math/big" bridges "github.com/smartcontractkit/chainlink/core/bridges" - bulletprooftxmanager "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - chainlink "github.com/smartcontractkit/chainlink/core/services/chainlink" config "github.com/smartcontractkit/chainlink/core/config" @@ -34,7 +32,7 @@ import ( sqlx "github.com/smartcontractkit/sqlx" - terratypes "github.com/smartcontractkit/chainlink/core/chains/terra/types" + txmgr "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" types "github.com/smartcontractkit/chainlink/core/chains/evm/types" @@ -64,22 +62,6 @@ func (_m *Application) AddJobV2(ctx context.Context, _a1 *job.Job) error { return r0 } -// BPTXMORM provides a mock function with given fields: -func (_m *Application) BPTXMORM() bulletprooftxmanager.ORM { - ret := _m.Called() - - var r0 bulletprooftxmanager.ORM - if rf, ok := ret.Get(0).(func() bulletprooftxmanager.ORM); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(bulletprooftxmanager.ORM) - } - } - - return r0 -} - // BridgeORM provides a mock function with given fields: func (_m *Application) BridgeORM() bridges.ORM { ret := _m.Called() @@ -346,13 +328,13 @@ func (_m *Application) PipelineORM() pipeline.ORM { return r0 } -// ReplayFromBlock provides a mock function with given fields: chainID, number -func (_m *Application) ReplayFromBlock(chainID *big.Int, number uint64) error { - ret := _m.Called(chainID, number) +// ReplayFromBlock provides a mock function with given fields: chainID, number, forceBroadcast +func (_m *Application) ReplayFromBlock(chainID *big.Int, number uint64, forceBroadcast bool) error { + ret := _m.Called(chainID, number, forceBroadcast) var r0 error - if rf, ok := ret.Get(0).(func(*big.Int, uint64) error); ok { - r0 = rf(chainID, number) + if rf, ok := ret.Get(0).(func(*big.Int, uint64, bool) error); ok { + r0 = rf(chainID, number, forceBroadcast) } else { r0 = ret.Error(0) } @@ -460,13 +442,13 @@ func (_m *Application) SetServiceLogLevel(ctx context.Context, service string, l return r0 } -// Start provides a mock function with given fields: -func (_m *Application) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: ctx +func (_m *Application) Start(ctx context.Context) error { + ret := _m.Called(ctx) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) } else { r0 = ret.Error(0) } @@ -488,16 +470,16 @@ func (_m *Application) Stop() error { return r0 } -// TerraORM provides a mock function with given fields: -func (_m *Application) TerraORM() terratypes.ORM { +// TxmORM provides a mock function with given fields: +func (_m *Application) TxmORM() txmgr.ORM { ret := _m.Called() - var r0 terratypes.ORM - if rf, ok := ret.Get(0).(func() terratypes.ORM); ok { + var r0 txmgr.ORM + if rf, ok := ret.Get(0).(func() txmgr.ORM); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(terratypes.ORM) + r0 = ret.Get(0).(txmgr.ORM) } } diff --git a/core/internal/testutils/configtest/general_config.go b/core/internal/testutils/configtest/general_config.go index a048e1cc70d..fe18d6946e8 100644 --- a/core/internal/testutils/configtest/general_config.go +++ b/core/internal/testutils/configtest/general_config.go @@ -53,11 +53,7 @@ type GeneralConfigOverrides struct { Dev null.Bool ShutdownGracePeriod *time.Duration Dialect dialects.DialectName - EVMEnabled null.Bool - EVMRPCEnabled null.Bool EthereumURL null.String - FeatureExternalInitiators null.Bool - FeatureFeedsManager null.Bool GlobalBalanceMonitorEnabled null.Bool GlobalBlockEmissionIdleWarningThreshold *time.Duration GlobalChainType null.String @@ -98,14 +94,24 @@ type GeneralConfigOverrides struct { LogLevel *zapcore.Level DefaultLogLevel *zapcore.Level LogSQL null.Bool - LogToDisk null.Bool + LogFileMaxSize null.String + LogFileMaxAge null.Int + LogFileMaxBackups null.Int SecretGenerator config.SecretGenerator TriggerFallbackDBPollInterval *time.Duration KeySpecific map[string]types.ChainCfg - FeatureOffchainReporting null.Bool - FeatureOffchainReporting2 null.Bool LinkContractAddress null.String + // Feature Flags + FeatureExternalInitiators null.Bool + FeatureFeedsManager null.Bool + FeatureOffchainReporting null.Bool + FeatureOffchainReporting2 null.Bool + EVMEnabled null.Bool + EVMRPCEnabled null.Bool + TerraEnabled null.Bool + P2PEnabled null.Bool + // OCR v2 OCR2DatabaseTimeout *time.Duration @@ -275,6 +281,14 @@ func (c *TestGeneralConfig) EVMRPCEnabled() bool { return c.GeneralConfig.EVMRPCEnabled() } +// TerraEnabled allows Terra to be used +func (c *TestGeneralConfig) TerraEnabled() bool { + if c.Overrides.TerraEnabled.Valid { + return c.Overrides.TerraEnabled.Bool + } + return c.GeneralConfig.TerraEnabled() +} + func (c *TestGeneralConfig) EthereumURL() string { if c.Overrides.EthereumURL.Valid { return c.Overrides.EthereumURL.String @@ -351,6 +365,7 @@ func (c *TestGeneralConfig) FeatureOffchainReporting2() bool { return c.GeneralConfig.FeatureOffchainReporting2() } +// TriggerFallbackDBPollInterval returns the test configured value for TriggerFallbackDBPollInterval func (c *TestGeneralConfig) TriggerFallbackDBPollInterval() time.Duration { if c.Overrides.TriggerFallbackDBPollInterval != nil { return *c.Overrides.TriggerFallbackDBPollInterval @@ -358,11 +373,33 @@ func (c *TestGeneralConfig) TriggerFallbackDBPollInterval() time.Duration { return c.GeneralConfig.TriggerFallbackDBPollInterval() } -func (c *TestGeneralConfig) LogToDisk() bool { - if c.Overrides.LogToDisk.Valid { - return c.Overrides.LogToDisk.Bool +// LogFileMaxSize allows to override the log file's max size before file rotation. +func (c *TestGeneralConfig) LogFileMaxSize() utils.FileSize { + if c.Overrides.LogFileMaxSize.Valid { + var val utils.FileSize + + err := val.UnmarshalText([]byte(c.Overrides.LogFileMaxSize.String)) + require.NoError(c.t, err) + + return val + } + return c.GeneralConfig.LogFileMaxSize() +} + +// LogFileMaxAge allows to override the log file's max age before file rotation. +func (c *TestGeneralConfig) LogFileMaxAge() int64 { + if c.Overrides.LogFileMaxAge.Valid { + return c.Overrides.LogFileMaxAge.Int64 + } + return int64(c.GeneralConfig.LogFileMaxAge()) +} + +// LogFileMaxBackups allows to override the max amount of old log files to retain. +func (c *TestGeneralConfig) LogFileMaxBackups() int64 { + if c.Overrides.LogFileMaxBackups.Valid { + return c.Overrides.LogFileMaxBackups.Int64 } - return c.GeneralConfig.LogToDisk() + return int64(c.GeneralConfig.LogFileMaxBackups()) } func (c *TestGeneralConfig) AdminCredentialsFile() string { @@ -465,6 +502,14 @@ func (c *TestGeneralConfig) EVMEnabled() bool { return c.GeneralConfig.EVMEnabled() } +// P2PEnabled overrides +func (c *TestGeneralConfig) P2PEnabled() bool { + if c.Overrides.P2PEnabled.Valid { + return c.Overrides.P2PEnabled.Bool + } + return c.GeneralConfig.P2PEnabled() +} + func (c *TestGeneralConfig) GlobalGasEstimatorMode() (string, bool) { if c.Overrides.GlobalGasEstimatorMode.Valid { return c.Overrides.GlobalGasEstimatorMode.String, true diff --git a/core/internal/testutils/evmtest/evmtest.go b/core/internal/testutils/evmtest/evmtest.go index afc6425541d..3dcd1295d26 100644 --- a/core/internal/testutils/evmtest/evmtest.go +++ b/core/internal/testutils/evmtest/evmtest.go @@ -9,11 +9,11 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" @@ -30,7 +30,7 @@ type TestChainOpts struct { ChainCfg evmtypes.ChainCfg HeadTracker httypes.HeadTracker DB *sqlx.DB - TxManager bulletprooftxmanager.TxManager + TxManager txmgr.TxManager KeyStore keystore.Eth } @@ -64,7 +64,7 @@ func NewChainSet(t testing.TB, testopts TestChainOpts) evm.ChainSet { } } if testopts.TxManager != nil { - opts.GenTxManager = func(evmtypes.Chain) bulletprooftxmanager.TxManager { + opts.GenTxManager = func(evmtypes.Chain) txmgr.TxManager { return testopts.TxManager } @@ -161,19 +161,23 @@ func (mo *MockORM) DeleteNode(id int64) error { panic("not implemented") } -func (mo *MockORM) Nodes(offset int, limit int) ([]evmtypes.Node, int, error) { +// Nodes implements evmtypes.ORM +func (mo *MockORM) Nodes(offset int, limit int, qopts ...pg.QOpt) ([]evmtypes.Node, int, error) { panic("not implemented") } -func (mo *MockORM) Node(id int32) (evmtypes.Node, error) { +// Node implements evmtypes.ORM +func (mo *MockORM) Node(id int32, qopts ...pg.QOpt) (evmtypes.Node, error) { panic("not implemented") } -func (mo *MockORM) GetNodesByChainIDs(chainIDs []utils.Big) (nodes []evmtypes.Node, err error) { +// GetNodesByChainIDs implements evmtypes.ORM +func (mo *MockORM) GetNodesByChainIDs(chainIDs []utils.Big, qopts ...pg.QOpt) (nodes []evmtypes.Node, err error) { panic("not implemented") } -func (mo *MockORM) NodesForChain(chainID utils.Big, offset int, limit int) ([]evmtypes.Node, int, error) { +// NodesForChain implements evmtypes.ORM +func (mo *MockORM) NodesForChain(chainID utils.Big, offset int, limit int, qopts ...pg.QOpt) ([]evmtypes.Node, int, error) { panic("not implemented") } diff --git a/core/internal/testutils/pgtest/pgtest.go b/core/internal/testutils/pgtest/pgtest.go index fb6652676ee..aabe226f449 100644 --- a/core/internal/testutils/pgtest/pgtest.go +++ b/core/internal/testutils/pgtest/pgtest.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -22,6 +23,7 @@ func NewPGCfg(logSQL bool) pg.LogConfig { return PGCfg{logSQL} } func (p PGCfg) LogSQL() bool { return p.logSQL } func NewSqlDB(t *testing.T) *sql.DB { + testutils.SkipShortDB(t) db, err := sql.Open("txdb", uuid.NewV4().String()) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, db.Close()) }) @@ -30,6 +32,7 @@ func NewSqlDB(t *testing.T) *sql.DB { } func NewSqlxDB(t *testing.T) *sqlx.DB { + testutils.SkipShortDB(t) db, err := sqlx.Open("txdb", uuid.NewV4().String()) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, db.Close()) }) diff --git a/core/internal/testutils/terratest/terratest.go b/core/internal/testutils/terratest/terratest.go index b5af4c254e0..68673be80bf 100644 --- a/core/internal/testutils/terratest/terratest.go +++ b/core/internal/testutils/terratest/terratest.go @@ -1,6 +1,8 @@ package terratest import ( + "fmt" + "math/rand" "testing" "github.com/smartcontractkit/sqlx" @@ -17,3 +19,8 @@ INSERT INTO terra_chains (id, cfg, enabled, created_at, updated_at) VALUES (:id, err := db.Get(chain, query, args...) require.NoError(t, err) } + +// RandomChainID returns a random chain id for testing. Use this instead of a constant to prevent DB collisions. +func RandomChainID() string { + return fmt.Sprintf("Chainlinktest-%d", rand.Int31n(999999)) +} diff --git a/core/internal/testutils/testutils.go b/core/internal/testutils/testutils.go index a7a5299aecf..bdd08851eca 100644 --- a/core/internal/testutils/testutils.go +++ b/core/internal/testutils/testutils.go @@ -2,16 +2,30 @@ package testutils import ( "context" + "fmt" + "log" + "math" "math/big" mrand "math/rand" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" "testing" "time" "github.com/ethereum/go-ethereum/common" -) + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/gorilla/websocket" + "github.com/tidwall/gjson" + "go.uber.org/zap/zaptest/observer" -// NOTE: To avoid circular dependencies, this package may not import anything -// from "github.com/smartcontractkit/chainlink/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + // NOTE: To avoid circular dependencies, this package MUST NOT import + // anything from "github.com/smartcontractkit/chainlink/core" +) // FixtureChainID matches the chain always added by fixtures.sql // It is set to 0 since no real chain ever has this ID and allows a virtual @@ -23,6 +37,26 @@ func NewAddress() common.Address { return common.BytesToAddress(randomBytes(20)) } +// NewRandomInt64 returns a (non-cryptographically secure) random positive int64 +func NewRandomInt64() int64 { + id := mrand.Int63() + return id +} + +// NewRandomEVMChainID returns a suitable random chain ID that will not conflict +// with fixtures +func NewRandomEVMChainID() *big.Int { + id := mrand.Int63n(math.MaxInt32) + 10000 + return big.NewInt(id) +} + +// TestCtx is a context that will expire on test timeout +func TestCtx(t *testing.T) context.Context { + ctx, cancel := context.WithTimeout(context.Background(), WaitTimeout(t)) + t.Cleanup(cancel) + return ctx +} + func randomBytes(n int) []byte { b := make([]byte, n) _, _ = mrand.Read(b) // Assignment for errcheck. Only used in tests so we can ignore. @@ -59,3 +93,242 @@ func Context(t *testing.T) (ctx context.Context) { } return ctx } + +// MustParseURL parses the URL or fails the test +func MustParseURL(t *testing.T, input string) *url.URL { + u, err := url.Parse(input) + require.NoError(t, err) + return u +} + +// JSONRPCHandler is called with the method and request param(s). +// respResult will be sent immediately. notifyResult is optional, and sent after a short delay. +type JSONRPCHandler func(reqMethod string, reqParams gjson.Result) (respResult, notifyResult string) + +type testWSServer struct { + t *testing.T + s *httptest.Server + mu sync.RWMutex + wsconns []*websocket.Conn +} + +// NewWSServer starts a websocket server which invokes callback for each message received. +// If chainID is set, then eth_chainId calls will be automatically handled. +func NewWSServer(t *testing.T, chainID *big.Int, callback JSONRPCHandler) (ts *testWSServer) { + ts = new(testWSServer) + ts.t = t + ts.wsconns = make([]*websocket.Conn, 0) + handler := ts.newWSHandler(chainID, callback) + ts.s = httptest.NewServer(handler) + return +} + +func (ts *testWSServer) Close() { + ts.mu.Lock() + defer ts.mu.Unlock() + if ts.wsconns == nil { + ts.t.Log("Test WS server already closed") + return + } + ts.s.CloseClientConnections() + ts.s.Close() + for _, ws := range ts.wsconns { + ws.Close() + } + ts.wsconns = nil // nil indicates server closed +} + +func (ts *testWSServer) WSURL() *url.URL { + return WSServerURL(ts.t, ts.s) +} + +func (ts *testWSServer) GetConns(t *testing.T) (conns []*websocket.Conn) { + ts.mu.RLock() + defer ts.mu.RUnlock() + if ts.wsconns == nil { + t.Fatal("cannot get conns from closed server") + } + conns = append(conns, ts.wsconns...) + return +} + +func (ts *testWSServer) MustWriteBinaryMessageSync(t *testing.T, msg string) { + ts.mu.Lock() + defer ts.mu.Unlock() + conns := ts.wsconns + if len(conns) != 1 { + t.Fatalf("expected 1 conn, got %d", len(conns)) + } + conn := conns[0] + err := conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + require.NoError(t, err) +} + +func (ts *testWSServer) newWSHandler(chainID *big.Int, callback JSONRPCHandler) (handler http.HandlerFunc) { + t := ts.t + upgrader := websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, + } + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + require.NoError(t, err, "Failed to upgrade WS connection") + defer conn.Close() + ts.mu.Lock() + if ts.wsconns == nil { + log.Println("Server closed") + ts.mu.Unlock() + return + } + ts.wsconns = append(ts.wsconns, conn) + ts.mu.Unlock() + for { + _, data, err := conn.ReadMessage() + if err != nil { + if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseAbnormalClosure) { + log.Println("Websocket closing") + return + } + log.Printf("Failed to read message: %v", err) + return + } + log.Println("Received message", string(data)) + req := gjson.ParseBytes(data) + if !req.IsObject() { + log.Printf("Request must be object: %v", req.Type) + return + } + if e := req.Get("error"); e.Exists() { + log.Printf("Received jsonrpc error message: %v", e) + break + } + m := req.Get("method") + if m.Type != gjson.String { + log.Printf("Method must be string: %v", m.Type) + return + } + + var resp, notify string + if chainID != nil && m.String() == "eth_chainId" { + resp = `"0x` + chainID.Text(16) + `"` + } else { + resp, notify = callback(m.String(), req.Get("params")) + } + id := req.Get("id") + msg := fmt.Sprintf(`{"jsonrpc":"2.0","id":%s,"result":%s}`, id, resp) + log.Printf("Sending message: %v", msg) + ts.mu.Lock() + err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + ts.mu.Unlock() + if err != nil { + log.Printf("Failed to write message: %v", err) + return + } + + if notify != "" { + time.Sleep(100 * time.Millisecond) + msg := fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":%s}}`, notify) + log.Println("Sending message", msg) + ts.mu.Lock() + err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + ts.mu.Unlock() + if err != nil { + log.Printf("Failed to write message: %v", err) + return + } + } + } + }) + return handler +} + +// WaitWithTimeout waits for the channel to close (or receive anything) and +// fatals the test if the default wait timeout is exceeded +func WaitWithTimeout(t *testing.T, ch <-chan struct{}, failMsg string) { + select { + case <-ch: + case <-time.After(WaitTimeout(t)): + t.Fatal(failMsg) + } +} + +// WSServerURL returns a ws:// url for the server +func WSServerURL(t *testing.T, s *httptest.Server) *url.URL { + u, err := url.Parse(s.URL) + require.NoError(t, err, "Failed to parse url") + u.Scheme = "ws" + return u +} + +// IntToHex converts int to geth-compatible hex +func IntToHex(n int) string { + return hexutil.EncodeBig(big.NewInt(int64(n))) +} + +// TestInterval is just a sensible poll interval that gives fast tests without +// risk of spamming +const TestInterval = 10 * time.Millisecond + +// AssertEventually waits for f to return true +func AssertEventually(t *testing.T, f func() bool) { + assert.Eventually(t, f, WaitTimeout(t), TestInterval/2) +} + +// RequireLogMessage fails the test if emitted logs don't contain the given message +func RequireLogMessage(t *testing.T, observedLogs *observer.ObservedLogs, msg string) { + for _, l := range observedLogs.All() { + if strings.Contains(l.Message, msg) { + return + } + } + t.Log("observed logs", observedLogs.All()) + t.Fatalf("expected observed logs to contain msg %q, but it didn't", msg) +} + +// WaitForLogMessage waits until at least one log message containing the +// specified msg is emitted. +// NOTE: This does not "pop" messages so it cannot be used multiple times to +// check for new instances of the same msg. See WaitForLogMessageCount instead. +// +// Get a *observer.ObservedLogs like so: +// +// observedZapCore, observedLogs := observer.New(zap.DebugLevel) +// lggr := logger.TestLogger(t, observedZapCore) +func WaitForLogMessage(t *testing.T, observedLogs *observer.ObservedLogs, msg string) { + AssertEventually(t, func() bool { + for _, l := range observedLogs.All() { + if strings.Contains(l.Message, msg) { + return true + } + } + return false + }) +} + +// WaitForLogMessageCount waits until at least count log message containing the +// specified msg is emitted +func WaitForLogMessageCount(t *testing.T, observedLogs *observer.ObservedLogs, msg string, count int) { + i := 0 + AssertEventually(t, func() bool { + for _, l := range observedLogs.All() { + if strings.Contains(l.Message, msg) { + i++ + if i >= count { + return true + } + } + } + return false + }) +} + +// SkipShort skips tb during -short runs, and notes why. +func SkipShort(tb testing.TB, why string) { + if testing.Short() { + tb.Skipf("skipping: %s", why) + } +} + +// SkipShortDB skips tb during -short runs, and notes the DB dependency. +func SkipShortDB(tb testing.TB) { + SkipShort(tb, "DB dependency") +} diff --git a/core/logger/internal/colortest/prettyconsole_test.go b/core/logger/internal/colortest/prettyconsole_test.go index 22ca79d57d9..fa2ad055a98 100644 --- a/core/logger/internal/colortest/prettyconsole_test.go +++ b/core/logger/internal/colortest/prettyconsole_test.go @@ -3,8 +3,9 @@ package colortest import ( "testing" - "github.com/smartcontractkit/chainlink/core/logger" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/core/logger" ) func init() { @@ -19,24 +20,48 @@ func TestPrettyConsole_Write(t *testing.T) { wantError bool }{ { - "headline", - `{"ts":1523537728.7260377, "level":"info", "msg":"top level"}`, - "2018-04-12T12:55:28Z \x1b[37m[INFO] \x1b[0mtop level \x1b[34m\x1b[0m \n", + "debug", + `{"ts":1523537728, "level":"debug", "msg":"top level", "details":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[32m[DEBUG] \x1b[0mtop level \x1b[34m\x1b[0m \x1b[32mdetails\x1b[0m=nuances \n", false, }, { - "details", - `{"ts":1523537728, "level":"debug", "msg":"top level", "details":"nuances"}`, - "2018-04-12T12:55:28Z \x1b[32m[DEBUG] \x1b[0mtop level \x1b[34m\x1b[0m \x1b[32mdetails\x1b[0m=nuances \n", + "info", + `{"ts":1523537728.7260377, "level":"info", "msg":"top level"}`, + "2018-04-12T12:55:28Z \x1b[37m[INFO] \x1b[0mtop level \x1b[34m\x1b[0m \n", false, }, { - "blacklist", + "warn", `{"ts":1523537728, "level":"warn", "msg":"top level", "hash":"nuances"}`, "2018-04-12T12:55:28Z \x1b[33m[WARN] \x1b[0mtop level \x1b[34m\x1b[0m \n", false, }, - {"error", `{"broken":}`, `{}`, true}, + { + "error", + `{"ts":1523537728, "level":"error", "msg":"top level", "hash":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[31m[ERROR] \x1b[0mtop level \x1b[34m\x1b[0m \n", + false, + }, + { + "critical", + `{"ts":1523537728, "level":"crit", "msg":"top level", "hash":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[91m[CRIT] \x1b[0mtop level \x1b[34m\x1b[0m \n", + false, + }, + { + "panic", + `{"ts":1523537728, "level":"panic", "msg":"top level", "hash":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[91m[PANIC] \x1b[0mtop level \x1b[34m\x1b[0m \n", + false, + }, + { + "fatal", + `{"ts":1523537728, "level":"fatal", "msg":"top level", "hash":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[91m[FATAL] \x1b[0mtop level \x1b[34m\x1b[0m \n", + false, + }, + {"broken", `{"broken":}`, `{}`, true}, } for _, tt := range tests { @@ -48,6 +73,7 @@ func TestPrettyConsole_Write(t *testing.T) { if tt.wantError { assert.Error(t, err) } else { + t.Log(tr.Written) assert.Equal(t, tt.want, tr.Written) } }) diff --git a/core/logger/logger.go b/core/logger/logger.go index 64c355086af..71d0a62c596 100644 --- a/core/logger/logger.go +++ b/core/logger/logger.go @@ -12,8 +12,12 @@ import ( "github.com/smartcontractkit/chainlink/core/config/envvar" "github.com/smartcontractkit/chainlink/core/static" + "github.com/smartcontractkit/chainlink/core/utils" ) +// LogsFile describes the logs file name +const LogsFile = "chainlink_debug.log" + func init() { err := zap.RegisterSink("pretty", prettyConsoleSink(os.Stderr)) if err != nil { @@ -115,14 +119,12 @@ type Logger interface { // Constants for service names for package specific logging configuration const ( - HeadTracker = "HeadTracker" - HeadListener = "HeadListener" - HeadSaver = "HeadSaver" - HeadBroadcaster = "HeadBroadcaster" - FluxMonitor = "FluxMonitor" - Keeper = "Keeper" - TelemetryIngressBatchClient = "TelemetryIngressBatchClient" - TelemetryIngressBatchWorker = "TelemetryIngressBatchWorker" + HeadTracker = "HeadTracker" + HeadListener = "HeadListener" + HeadSaver = "HeadSaver" + HeadBroadcaster = "HeadBroadcaster" + FluxMonitor = "FluxMonitor" + Keeper = "Keeper" ) func GetLogServices() []string { @@ -133,20 +135,15 @@ func GetLogServices() []string { } } -// newProductionConfig returns a new production zap.Config. -func newProductionConfig(dir string, jsonConsole bool, toDisk bool, unixTS bool) zap.Config { - config := newBaseConfig() +// newZapConfigProd returns a new production zap.Config. +func newZapConfigProd(jsonConsole bool, unixTS bool) zap.Config { + config := newZapConfigBase() if !unixTS { config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder } if !jsonConsole { config.OutputPaths = []string{"pretty://console"} } - if toDisk { - destination := logFileURI(dir) - config.OutputPaths = append(config.OutputPaths, destination) - config.ErrorOutputPaths = append(config.ErrorOutputPaths, destination) - } return config } @@ -168,7 +165,7 @@ func verShaName(ver, sha string) string { // NewLogger returns a new Logger configured from environment variables, and logs any parsing errors. // Tests should use TestLogger. -func NewLogger() Logger { +func NewLogger() (Logger, func() error) { var c Config var parseErrs []string @@ -192,42 +189,80 @@ func NewLogger() Logger { parseErrs = append(parseErrs, invalid) } - c.ToDisk, invalid = envvar.LogToDisk.ParseBool() + var fileMaxSize utils.FileSize + fileMaxSize, invalid = envvar.LogFileMaxSize.ParseFileSize() + c.FileMaxSize = int(fileMaxSize) if invalid != "" { parseErrs = append(parseErrs, invalid) } + if c.DebugLogsToDisk() { + var ( + fileMaxAge int64 + maxBackups int64 + ) + + fileMaxAge, invalid = envvar.LogFileMaxAge.ParseInt64() + c.FileMaxAge = int(fileMaxAge) + if invalid != "" { + parseErrs = append(parseErrs, invalid) + } + + maxBackups, invalid = envvar.LogFileMaxBackups.ParseInt64() + c.FileMaxBackups = int(maxBackups) + if invalid != "" { + parseErrs = append(parseErrs, invalid) + } + } + c.UnixTS, invalid = envvar.LogUnixTS.ParseBool() if invalid != "" { parseErrs = append(parseErrs, invalid) } - l := c.New() + l, close := c.New() for _, msg := range parseErrs { l.Error(msg) } - return l.Named(verShaNameStatic()) + return l.Named(verShaNameStatic()), close } type Config struct { - LogLevel zapcore.Level - Dir string - JsonConsole bool - ToDisk bool // if false, the Logger will only log to stdout. - UnixTS bool + LogLevel zapcore.Level + Dir string + JsonConsole bool + UnixTS bool + FileMaxSize int // megabytes + FileMaxAge int // days + FileMaxBackups int // files } // New returns a new Logger with pretty printing to stdout, prometheus counters, and sentry forwarding. // Tests should use TestLogger. -func (c *Config) New() Logger { - cfg := newProductionConfig(c.Dir, c.JsonConsole, c.ToDisk, c.UnixTS) +func (c *Config) New() (Logger, func() error) { + cfg := newZapConfigProd(c.JsonConsole, c.UnixTS) cfg.Level.SetLevel(c.LogLevel) - l, err := newZapLogger(cfg) + l, close, err := zapLoggerConfig{ + local: *c, + Config: cfg, + diskStats: utils.NewDiskStatsProvider(), + diskPollConfig: newDiskPollConfig(diskPollInterval), + }.newLogger() if err != nil { log.Fatal(err) } l = newSentryLogger(l) - return newPrometheusLogger(l) + return newPrometheusLogger(l), close +} + +// DebugLogsToDisk returns whether debug logs should be stored in disk +func (c Config) DebugLogsToDisk() bool { + return c.FileMaxSize > 0 +} + +// RequiredDiskSpace returns the required disk space in order to allow debug logs to be stored in disk +func (c Config) RequiredDiskSpace() utils.FileSize { + return utils.FileSize(c.FileMaxSize * (c.FileMaxBackups + 1)) } // InitColor explicitly sets the global color.NoColor option. @@ -236,8 +271,8 @@ func InitColor(c bool) { color.NoColor = !c } -// newBaseConfig returns a zap.NewProductionConfig with sampling disabled and a modified level encoder. -func newBaseConfig() zap.Config { +// newZapConfigBase returns a zap.NewProductionConfig with sampling disabled and a modified level encoder. +func newZapConfigBase() zap.Config { cfg := zap.NewProductionConfig() cfg.Sampling = nil cfg.EncoderConfig.EncodeLevel = encodeLevel diff --git a/core/logger/logger_test.go b/core/logger/logger_test.go index 62b5ead4fec..6e305ec0328 100644 --- a/core/logger/logger_test.go +++ b/core/logger/logger_test.go @@ -8,14 +8,14 @@ import ( func TestConfig(t *testing.T) { // no sampling - assert.Nil(t, newBaseConfig().Sampling) - assert.Nil(t, newTestConfig().Sampling) - assert.Nil(t, newProductionConfig("", false, true, false).Sampling) + assert.Nil(t, newZapConfigBase().Sampling) + assert.Nil(t, newZapConfigTest().Sampling) + assert.Nil(t, newZapConfigProd(false, false).Sampling) // not development, which would trigger panics for Critical level - assert.False(t, newBaseConfig().Development) - assert.False(t, newTestConfig().Development) - assert.False(t, newProductionConfig("", false, true, false).Development) + assert.False(t, newZapConfigBase().Development) + assert.False(t, newZapConfigTest().Development) + assert.False(t, newZapConfigProd(false, false).Development) } func Test_verShaName(t *testing.T) { diff --git a/core/logger/logger_unix.go b/core/logger/logger_unix.go index c0a02970029..ddf479eb171 100644 --- a/core/logger/logger_unix.go +++ b/core/logger/logger_unix.go @@ -12,5 +12,5 @@ func registerOSSinks() error { // logFileURI returns the full path to the file the // NewLogger logs to, and uses zap's built in default file sink. func logFileURI(configRootDir string) string { - return filepath.ToSlash(filepath.Join(configRootDir, "log.jsonl")) + return filepath.ToSlash(filepath.Join(configRootDir, LogsFile)) } diff --git a/core/logger/logger_windows.go b/core/logger/logger_windows.go index 98cb578db11..2a64fc488cc 100644 --- a/core/logger/logger_windows.go +++ b/core/logger/logger_windows.go @@ -16,7 +16,7 @@ import ( // Windows to get around their handling of the file:// schema in uber.org/zap. // https://github.com/uber-go/zap/issues/621 func logFileURI(configRootDir string) string { - return "winfile:///" + filepath.ToSlash(filepath.Join(configRootDir, "log.jsonl")) + return "winfile:///" + filepath.ToSlash(filepath.Join(configRootDir, LogsFile)) } func registerOSSinks() error { diff --git a/core/logger/model.go b/core/logger/model.go deleted file mode 100644 index c3781aee090..00000000000 --- a/core/logger/model.go +++ /dev/null @@ -1,14 +0,0 @@ -package logger - -import ( - "time" -) - -// LogConfig stores key value pairs for configuring package specific logging -type LogConfig struct { - ID int64 - ServiceName string - LogLevel string - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/core/logger/orm.go b/core/logger/orm.go index 579810671ec..b792cd01660 100644 --- a/core/logger/orm.go +++ b/core/logger/orm.go @@ -3,11 +3,21 @@ package logger import ( "context" "database/sql" + "time" "github.com/pkg/errors" "github.com/smartcontractkit/sqlx" ) +// LogConfig stores key value pairs for configuring package specific logging +type LogConfig struct { + ID int64 + ServiceName string + LogLevel string + CreatedAt time.Time + UpdatedAt time.Time +} + type ORM interface { GetServiceLogLevel(serviceName string) (level string, ok bool) SetServiceLogLevel(ctx context.Context, serviceName string, level string) error @@ -41,7 +51,7 @@ INSERT INTO log_configs ( service_name, log_level, created_at, updated_at ) VALUES ( $1, $2, NOW(), NOW() -) ON CONFLICT (service_name) +) ON CONFLICT (service_name) DO UPDATE SET log_level = EXCLUDED.log_level `, serviceName, level) return errors.Wrap(err, "LogOrm#SetServiceLogLevel failed") diff --git a/core/logger/prettyconsole.go b/core/logger/prettyconsole.go index 1fabeffd187..69427f74715 100644 --- a/core/logger/prettyconsole.go +++ b/core/logger/prettyconsole.go @@ -20,8 +20,9 @@ var levelColors = map[string]func(...interface{}) string{ "info": color.New(color.FgWhite).SprintFunc(), "warn": color.New(color.FgYellow).SprintFunc(), "error": color.New(color.FgRed).SprintFunc(), - "panic": color.New(color.FgRed).SprintFunc(), - "fatal": color.New(color.FgRed).SprintFunc(), + "panic": color.New(color.FgHiRed).SprintFunc(), + "crit": color.New(color.FgHiRed).SprintFunc(), + "fatal": color.New(color.FgHiRed).SprintFunc(), } var blue = color.New(color.FgBlue).SprintFunc() diff --git a/core/logger/sugared.go b/core/logger/sugared.go new file mode 100644 index 00000000000..b2937c7201b --- /dev/null +++ b/core/logger/sugared.go @@ -0,0 +1,36 @@ +package logger + +// SugaredLogger extends the base Logger interface with syntactic sugar, similar to zap.SugaredLogger. +type SugaredLogger interface { + Logger + AssumptionViolation(args ...interface{}) + AssumptionViolationf(format string, vals ...interface{}) + AssumptionViolationw(msg string, keyvals ...interface{}) +} + +func Sugared(l Logger) SugaredLogger { + return &sugared{ + Logger: l, + h: l.Helper(1), + } +} + +type sugared struct { + Logger + h Logger // helper with stack trace skip level +} + +// AssumptionViolation wraps Error logs with assumption violation tag. +func (s *sugared) AssumptionViolation(args ...interface{}) { + s.h.Error(append([]interface{}{"AssumptionViolation:"}, args...)) +} + +// AssumptionViolationf wraps Errorf logs with assumption violation tag. +func (s *sugared) AssumptionViolationf(format string, vals ...interface{}) { + s.h.Errorf("AssumptionViolation: "+format, vals...) +} + +// AssumptionViolationw wraps Errorw logs with assumption violation tag. +func (s *sugared) AssumptionViolationw(msg string, keyvals ...interface{}) { + s.h.Errorw("AssumptionViolation: "+msg, keyvals...) +} diff --git a/core/logger/test_logger.go b/core/logger/test_logger.go index 331b861555f..e73c9d444e1 100644 --- a/core/logger/test_logger.go +++ b/core/logger/test_logger.go @@ -9,6 +9,10 @@ import ( "sync" "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" "github.com/smartcontractkit/chainlink/core/config/envvar" ) @@ -65,11 +69,22 @@ func MemoryLogTestingOnly() *MemorySink { // TestLogger creates a logger that directs output to PrettyConsole configured // for test output, and to the buffer testMemoryLog. t is optional. // Log level is derived from the LOG_LEVEL env var. -func TestLogger(t T) Logger { - cfg := newTestConfig() +func TestLogger(t T) SugaredLogger { + return testLogger(t) +} + +// TestLoggerObserved creates a logger with an observer that can be used to +// test emitted logs at the given level or above +func TestLoggerObserved(t T, lvl zapcore.Level) (Logger, *observer.ObservedLogs) { + observedZapCore, observedLogs := observer.New(lvl) + return testLogger(t, observedZapCore), observedLogs +} + +func testLogger(t T, cores ...zapcore.Core) SugaredLogger { + cfg := newZapConfigTest() ll, invalid := envvar.LogLevel.ParseLogLevel() cfg.Level.SetLevel(ll) - l, err := newZapLogger(cfg) + l, close, err := zapLoggerConfig{Config: cfg}.newLogger(cores...) if err != nil { if t == nil { log.Fatal(err) @@ -79,20 +94,27 @@ func TestLogger(t T) Logger { if invalid != "" { l.Error(invalid) } + if t != nil { + t.Cleanup(func() { + assert.NoError(t, close()) + }) + } if t == nil { - return l + return Sugared(l) } - return l.Named(verShaNameStatic()).Named(t.Name()) + return Sugared(l.Named(verShaNameStatic()).Named(t.Name())) } -func newTestConfig() zap.Config { +func newZapConfigTest() zap.Config { _ = MemoryLogTestingOnly() // Make sure memory log is created - config := newBaseConfig() + config := newZapConfigBase() config.OutputPaths = []string{"pretty://console", "memory://"} return config } type T interface { Name() string + Cleanup(f func()) Fatal(...interface{}) + Errorf(format string, args ...interface{}) } diff --git a/core/logger/zap.go b/core/logger/zap.go index 9bd60a407b7..5d8b43d1360 100644 --- a/core/logger/zap.go +++ b/core/logger/zap.go @@ -1,31 +1,118 @@ package logger import ( - "errors" "fmt" "io" "os" + "sync" + "github.com/smartcontractkit/chainlink/core/utils" + + "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var _ Logger = &zapLogger{} +// zapLoggerConfig defines the struct that serves as config when spinning up a the zap logger +type zapLoggerConfig struct { + zap.Config + local Config + diskLogLevel zap.AtomicLevel + diskStats utils.DiskStatsProvider + diskPollConfig zapDiskPollConfig + + // This is for tests only + testDiskLogLvlChan chan zapcore.Level +} + type zapLogger struct { *zap.SugaredLogger - config zap.Config - name string - fields []interface{} - callerSkip int + config zapLoggerConfig + name string + fields []interface{} + callerSkip int + pollDiskSpaceStop chan struct{} + pollDiskSpaceDone chan struct{} } -func newZapLogger(cfg zap.Config) (Logger, error) { - zl, err := cfg.Build() +func (cfg zapLoggerConfig) newLogger(cores ...zapcore.Core) (Logger, func() error, error) { + cfg.diskLogLevel = zap.NewAtomicLevelAt(zapcore.DebugLevel) + + newCore, errWriter, err := cfg.newCore() if err != nil { - return nil, err + return nil, nil, err + } + cores = append(cores, newCore) + if cfg.local.DebugLogsToDisk() { + diskCore, diskErr := cfg.newDiskCore() + if diskErr != nil { + return nil, nil, diskErr + } + cores = append(cores, diskCore) } - return &zapLogger{config: cfg, SugaredLogger: zl.Sugar()}, nil + + core := zapcore.NewTee(cores...) + lggr := &zapLogger{ + config: cfg, + pollDiskSpaceStop: make(chan struct{}), + pollDiskSpaceDone: make(chan struct{}), + SugaredLogger: zap.New(core, zap.ErrorOutput(errWriter)).Sugar(), + } + + if cfg.local.DebugLogsToDisk() { + go lggr.pollDiskSpace() + } + + var once sync.Once + close := func() error { + once.Do(func() { + if cfg.local.DebugLogsToDisk() { + close(lggr.pollDiskSpaceStop) + <-lggr.pollDiskSpaceDone + } + }) + + return lggr.Sync() + } + + return lggr, close, err +} + +func (cfg zapLoggerConfig) newCore() (zapcore.Core, zapcore.WriteSyncer, error) { + encoder := zapcore.NewJSONEncoder(makeEncoderConfig(cfg.local)) + + sink, closeOut, err := zap.Open(cfg.OutputPaths...) + if err != nil { + return nil, nil, err + } + + errSink, _, err := zap.Open(cfg.ErrorOutputPaths...) + if err != nil { + closeOut() + return nil, nil, err + } + + if cfg.Level == (zap.AtomicLevel{}) { + return nil, nil, errors.New("missing Level") + } + + filteredLogLevels := zap.LevelEnablerFunc(cfg.Level.Enabled) + + return zapcore.NewCore(encoder, sink, filteredLogLevels), errSink, nil +} + +func makeEncoderConfig(cfg Config) zapcore.EncoderConfig { + encoderConfig := zap.NewProductionEncoderConfig() + + if !cfg.UnixTS { + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + } + + encoderConfig.EncodeLevel = encodeLevel + + return encoderConfig } func (l *zapLogger) SetLogLevel(lvl zapcore.Level) { @@ -65,13 +152,26 @@ func (l *zapLogger) Named(name string) Logger { func (l *zapLogger) NewRootLogger(lvl zapcore.Level) (Logger, error) { newLogger := *l newLogger.config.Level = zap.NewAtomicLevelAt(lvl) - zl, err := newLogger.config.Build() + newCore, errWriter, err := newLogger.config.newCore() if err != nil { return nil, err } - zl = zl.WithOptions(zap.AddCallerSkip(l.callerSkip)) - newLogger.SugaredLogger = zl.Named(l.name).Sugar().With(l.fields...) - return &newLogger, nil + cores := []zapcore.Core{ + // The console core is what we want to be unique per root, so we spin a new one here + newCore, + } + if newLogger.config.local.DebugLogsToDisk() { + diskCore, diskErr := newLogger.config.newDiskCore() + if diskErr != nil { + return nil, diskErr + } + cores = append(cores, diskCore) + } + core := zap.New(zapcore.NewTee(cores...), zap.ErrorOutput(errWriter), zap.AddCallerSkip(l.callerSkip)) + + newLogger.SugaredLogger = core.Named(l.name).Sugar().With(l.fields...) + + return &newLogger, err } func (l *zapLogger) Helper(skip int) Logger { diff --git a/core/logger/zap_disk_logging.go b/core/logger/zap_disk_logging.go new file mode 100644 index 00000000000..b2786cfa5a8 --- /dev/null +++ b/core/logger/zap_disk_logging.go @@ -0,0 +1,95 @@ +package logger + +import ( + "time" + + "github.com/smartcontractkit/chainlink/core/utils" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +const ( + // `Fatal` is the max log level allowed, so log levels like `Panic` or `Critical` won't be logged to disk if this is set. + disabledLevel = zapcore.FatalLevel + 1 + + diskPollInterval = 1 * time.Minute +) + +type zapDiskPollConfig struct { + stop func() + pollChan <-chan time.Time +} + +func newDiskPollConfig(interval time.Duration) zapDiskPollConfig { + ticker := time.NewTicker(utils.WithJitter(interval)) + + return zapDiskPollConfig{ + pollChan: ticker.C, + stop: ticker.Stop, + } +} + +func (cfg zapLoggerConfig) newDiskCore() (zapcore.Core, error) { + availableSpace, err := cfg.diskStats.AvailableSpace(cfg.local.Dir) + if err != nil || availableSpace < cfg.local.RequiredDiskSpace() { + // Won't log to disk if the directory is not found or there's not enough disk space + cfg.diskLogLevel.SetLevel(disabledLevel) + } + + var ( + encoder = zapcore.NewConsoleEncoder(makeEncoderConfig(cfg.local)) + sink = zapcore.AddSync(&lumberjack.Logger{ + Filename: logFileURI(cfg.local.Dir), + MaxSize: cfg.local.FileMaxSize, + MaxAge: cfg.local.FileMaxAge, + MaxBackups: cfg.local.FileMaxBackups, + Compress: true, + }) + allLogLevels = zap.LevelEnablerFunc(cfg.diskLogLevel.Enabled) + ) + + return zapcore.NewCore(encoder, sink, allLogLevels), nil +} + +func (l *zapLogger) pollDiskSpace() { + defer l.config.diskPollConfig.stop() + defer close(l.pollDiskSpaceDone) + + for { + select { + case <-l.pollDiskSpaceStop: + return + case <-l.config.diskPollConfig.pollChan: + lvl := zapcore.DebugLevel + + diskUsage, err := l.config.diskStats.AvailableSpace(l.config.local.Dir) + if err != nil { + // Will no longer log to disk + lvl = disabledLevel + l.Warnw("Error getting disk space available for logging", "err", err) + } else if diskUsage < l.config.local.RequiredDiskSpace() { + // Will no longer log to disk + lvl = disabledLevel + l.Warnf( + "Disk space is not enough to log into disk any longer, required disk space: %s, Available disk space: %s", + l.config.local.RequiredDiskSpace(), + diskUsage, + ) + } + + lvlBefore := l.config.diskLogLevel.Level() + + l.config.diskLogLevel.SetLevel(lvl) + + if lvlBefore == disabledLevel && lvl == zapcore.DebugLevel { + l.Info("Resuming disk logs, disk has enough space") + } + + if l.config.testDiskLogLvlChan != nil { + l.config.testDiskLogLvlChan <- lvl + } + } + } +} diff --git a/core/logger/zap_test.go b/core/logger/zap_test.go new file mode 100644 index 00000000000..2d6c2fc5470 --- /dev/null +++ b/core/logger/zap_test.go @@ -0,0 +1,225 @@ +package logger + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/smartcontractkit/chainlink/core/config/envvar" + "github.com/smartcontractkit/chainlink/core/utils" + utilsmocks "github.com/smartcontractkit/chainlink/core/utils/mocks" + + "github.com/stretchr/testify/assert" + "github.com/test-go/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func TestZapLogger_OutOfDiskSpace(t *testing.T) { + cfg := newZapConfigTest() + ll, invalid := envvar.LogLevel.ParseLogLevel() + assert.Empty(t, invalid) + + cfg.Level.SetLevel(ll) + + maxSize, invalid := envvar.LogFileMaxSize.ParseFileSize() + assert.Empty(t, invalid) + + logsDir := t.TempDir() + tmpFile, err := os.CreateTemp(logsDir, "*") + assert.NoError(t, err) + defer tmpFile.Close() + + var logFileSize utils.FileSize + err = logFileSize.UnmarshalText([]byte("100mb")) + assert.NoError(t, err) + + pollCfg := newDiskPollConfig(1 * time.Second) + zapCfg := zapLoggerConfig{ + Config: cfg, + local: Config{ + Dir: logsDir, + FileMaxAge: 0, + FileMaxBackups: 1, + FileMaxSize: int(logFileSize), + }, + diskPollConfig: pollCfg, + diskLogLevel: zap.NewAtomicLevelAt(zapcore.DebugLevel), + } + + t.Run("on logger creation", func(t *testing.T) { + diskMock := &utilsmocks.DiskStatsProvider{} + diskMock.On("AvailableSpace", logsDir).Return(maxSize, nil) + defer diskMock.AssertExpectations(t) + + pollChan := make(chan time.Time) + stop := func() { + close(pollChan) + } + + zapCfg.diskStats = diskMock + zapCfg.testDiskLogLvlChan = make(chan zapcore.Level) + zapCfg.diskPollConfig = zapDiskPollConfig{ + stop: stop, + pollChan: pollChan, + } + zapCfg.local.FileMaxSize = int(maxSize) * 2 + + lggr, close, err := zapCfg.newLogger() + assert.NoError(t, err) + defer close() + + pollChan <- time.Now() + <-zapCfg.testDiskLogLvlChan + + lggr.Debug("trying to write to disk when the disk logs should not be created") + + logFile := filepath.Join(zapCfg.local.Dir, LogsFile) + _, err = ioutil.ReadFile(logFile) + + require.Error(t, err) + require.Contains(t, err.Error(), "no such file or directory") + }) + + t.Run("on logger creation generic error", func(t *testing.T) { + diskMock := &utilsmocks.DiskStatsProvider{} + diskMock.On("AvailableSpace", logsDir).Return(utils.FileSize(0), fmt.Errorf("custom error")) + defer diskMock.AssertExpectations(t) + + pollChan := make(chan time.Time) + stop := func() { + close(pollChan) + } + + zapCfg.diskStats = diskMock + zapCfg.testDiskLogLvlChan = make(chan zapcore.Level) + zapCfg.diskPollConfig = zapDiskPollConfig{ + stop: stop, + pollChan: pollChan, + } + zapCfg.local.FileMaxSize = int(maxSize) * 2 + + lggr, close, err := zapCfg.newLogger() + assert.NoError(t, err) + defer close() + + pollChan <- time.Now() + <-zapCfg.testDiskLogLvlChan + + lggr.Debug("trying to write to disk when the disk logs should not be created - generic error") + + logFile := filepath.Join(zapCfg.local.Dir, LogsFile) + _, err = ioutil.ReadFile(logFile) + + require.Error(t, err) + require.Contains(t, err.Error(), "no such file or directory") + }) + + t.Run("after logger is created", func(t *testing.T) { + diskMock := &utilsmocks.DiskStatsProvider{} + diskMock.On("AvailableSpace", logsDir).Return(maxSize*10, nil).Once() + defer diskMock.AssertExpectations(t) + + pollChan := make(chan time.Time) + stop := func() { + close(pollChan) + } + + zapCfg.testDiskLogLvlChan = make(chan zapcore.Level) + zapCfg.diskStats = diskMock + zapCfg.diskPollConfig = zapDiskPollConfig{ + stop: stop, + pollChan: pollChan, + } + zapCfg.local.FileMaxSize = int(maxSize) * 2 + + lggr, close, err := zapCfg.newLogger() + assert.NoError(t, err) + defer close() + + lggr.Debug("writing to disk on test") + + diskMock.On("AvailableSpace", logsDir).Return(maxSize, nil) + + pollChan <- time.Now() + <-zapCfg.testDiskLogLvlChan + + lggr.SetLogLevel(zapcore.WarnLevel) + lggr.Debug("writing to disk on test again") + lggr.Warn("writing to disk on test again") + + logFile := filepath.Join(zapCfg.local.Dir, LogsFile) + b, err := ioutil.ReadFile(logFile) + assert.NoError(t, err) + + logs := string(b) + lines := strings.Split(logs, "\n") + // the last line is a blank line, hence why using len(lines) - 2 makes sense + actualMessage := lines[len(lines)-2] + expectedMessage := fmt.Sprintf( + "Disk space is not enough to log into disk any longer, required disk space: %s, Available disk space: %s", + zapCfg.local.RequiredDiskSpace(), + maxSize, + ) + + require.Contains(t, actualMessage, expectedMessage) + }) + + t.Run("after logger is created, recovers disk space", func(t *testing.T) { + diskMock := &utilsmocks.DiskStatsProvider{} + diskMock.On("AvailableSpace", logsDir).Return(maxSize*10, nil).Once() + defer diskMock.AssertExpectations(t) + + pollChan := make(chan time.Time) + stop := func() { + close(pollChan) + } + + zapCfg.testDiskLogLvlChan = make(chan zapcore.Level) + zapCfg.diskStats = diskMock + zapCfg.diskPollConfig = zapDiskPollConfig{ + stop: stop, + pollChan: pollChan, + } + zapCfg.local.FileMaxSize = int(maxSize) * 2 + + lggr, close, err := zapCfg.newLogger() + assert.NoError(t, err) + defer close() + + lggr.Debug("test") + + diskMock.On("AvailableSpace", logsDir).Return(maxSize, nil).Once() + + pollChan <- time.Now() + <-zapCfg.testDiskLogLvlChan + + diskMock.On("AvailableSpace", logsDir).Return(maxSize*12, nil).Once() + + pollChan <- time.Now() + <-zapCfg.testDiskLogLvlChan + + lggr.Debug("test again") + + logFile := filepath.Join(zapCfg.local.Dir, LogsFile) + b, err := ioutil.ReadFile(logFile) + assert.NoError(t, err) + + logs := string(b) + lines := strings.Split(logs, "\n") + expectedMessage := fmt.Sprintf( + "Disk space is not enough to log into disk any longer, required disk space: %s, Available disk space: %s", + zapCfg.local.RequiredDiskSpace(), + maxSize, + ) + + // the last line is a blank line, hence why using len(lines) - N makes sense + require.Contains(t, lines[len(lines)-4], expectedMessage) + require.Contains(t, lines[len(lines)-3], "Resuming disk logs, disk has enough space") + require.Contains(t, lines[len(lines)-2], "test again") + }) +} diff --git a/core/main.go b/core/main.go index f703d3f999b..0c2842b1d00 100644 --- a/core/main.go +++ b/core/main.go @@ -23,7 +23,7 @@ func main() { func Run(client *cmd.Client, args ...string) { app := cmd.NewApp(client) client.Logger.ErrorIf(app.Run(args), "Error running app") - if err := client.Logger.Sync(); err != nil { + if err := client.CloseLogger(); err != nil { log.Fatal(err) } } @@ -31,7 +31,7 @@ func Run(client *cmd.Client, args ...string) { // NewProductionClient configures an instance of the CLI to be used // in production. func NewProductionClient() *cmd.Client { - lggr := logger.NewLogger() + lggr, closeLggr := logger.NewLogger() cfg := config.NewGeneralConfig(lggr) prompter := cmd.NewTerminalPrompter() @@ -41,7 +41,7 @@ func NewProductionClient() *cmd.Client { if credentialsFile := cfg.AdminCredentialsFile(); credentialsFile != "" { var err error sr, err = sessionRequestBuilder.Build(credentialsFile) - if err != nil && errors.Cause(err) != cmd.ErrNoCredentialFile && !os.IsNotExist(err) { + if err != nil && !errors.Is(errors.Cause(err), cmd.ErrNoCredentialFile) && !os.IsNotExist(err) { lggr.Fatalw("Error loading API credentials", "error", err, "credentialsFile", credentialsFile) } } @@ -49,6 +49,7 @@ func NewProductionClient() *cmd.Client { Renderer: cmd.RendererTable{Writer: os.Stdout}, Config: cfg, Logger: lggr, + CloseLogger: closeLggr, AppFactory: cmd.ChainlinkAppFactory{}, KeyStoreAuthenticator: cmd.TerminalKeyStoreAuthenticator{Prompter: prompter}, FallbackAPIInitializer: cmd.NewPromptingAPIInitializer(prompter), diff --git a/core/main_test.go b/core/main_test.go index 0c4857fbf6d..977e5c7a3c2 100644 --- a/core/main_test.go +++ b/core/main_test.go @@ -24,10 +24,12 @@ func run(args ...string) { t := &testing.T{} tc := cltest.NewTestGeneralConfig(t) tc.Overrides.Dev = null.BoolFrom(false) + lggr := logger.TestLogger(t) testClient := &cmd.Client{ Renderer: cmd.RendererTable{Writer: ioutil.Discard}, Config: tc, - Logger: logger.TestLogger(t), + Logger: lggr, + CloseLogger: lggr.Sync, AppFactory: cmd.ChainlinkAppFactory{}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), Runner: cmd.ChainlinkRunner{}, @@ -60,9 +62,10 @@ func ExampleRun() { // jobs Commands for managing Jobs // keys Commands for managing various types of keys used by the Chainlink node // node, local Commands for admin actions that must be run locally - // txs Commands for handling Ethereum transactions + // txs Commands for handling transactions // chains Commands for handling chain configuration // nodes Commands for handling node configuration + // forwarders Commands for managing forwarder addresses. // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: @@ -438,12 +441,29 @@ func ExampleRun_txs() { run("txs", "--help") // Output: // NAME: - // core.test txs - Commands for handling Ethereum transactions + // core.test txs - Commands for handling transactions // // USAGE: // core.test txs command [command options] [arguments...] // // COMMANDS: + // evm Commands for handling EVM transactions + // terra Commands for handling Terra transactions + // + // OPTIONS: + // --help, -h show help +} + +func ExampleRun_txs_evm() { + run("txs", "evm", "--help") + // Output: + // NAME: + // core.test txs evm - Commands for handling EVM transactions + // + // USAGE: + // core.test txs evm command [command options] [arguments...] + // + // COMMANDS: // create Send ETH (or wei) from node ETH account to destination . // list List the Ethereum Transactions in descending order // show get information on a specific Ethereum Transaction @@ -452,6 +472,22 @@ func ExampleRun_txs() { // --help, -h show help } +func ExampleRun_txs_terra() { + run("txs", "terra", "--help") + // Output: + // NAME: + // core.test txs terra - Commands for handling Terra transactions + // + // USAGE: + // core.test txs terra command [command options] [arguments...] + // + // COMMANDS: + // create Send Luna from node Terra account to destination . + // + // OPTIONS: + // --help, -h show help +} + func ExampleRun_chains() { run("chains", "--help") // Output: @@ -559,3 +595,21 @@ func ExampleRun_nodes_terra() { // OPTIONS: // --help, -h show help } + +func ExampleRun_forwarders() { + run("forwarders", "--help") + // Output: + // NAME: + // core.test forwarders - Commands for managing forwarder addresses. + // + // USAGE: + // core.test forwarders command [command options] [arguments...] + // + // COMMANDS: + // list List all stored forwarders addresses + // create Create a new forwarder + // delete Delete a forwarder address + // + // OPTIONS: + // --help, -h show help +} diff --git a/core/scripts/chaincli/.env.example b/core/scripts/chaincli/.env.example index 30804acf26b..36c15a7e740 100644 --- a/core/scripts/chaincli/.env.example +++ b/core/scripts/chaincli/.env.example @@ -6,7 +6,10 @@ KEEPERS= APPROVE_AMOUNT= GAS_LIMIT= FUND_CHAINLINK_NODE= - +# Keeper Node Info for create Job Spec on existing nodes +KEEPER_URLS= +KEEPER_EMAILS= +KEEPER_PASSWORDS= # Keeper registry config LINK_ETH_FEED= @@ -19,16 +22,18 @@ STALENESS_SECONDS= GAS_CEILING_MULTIPLIER= FALLBACK_GAS_PRICE= FALLBACK_LINK_PRICE= +MUST_TAKE_TURNS= -# Keepers config -# optional only provide KEEPER_REGISTRY_ADDRESS if using an existing registry address -KEEPER_REGISTRY_ADDRESS= -# set KEEPER_CONFIG_UPDATE to true if providing KEEPER_REGISTRY_ADDRESS and want the config fields updated -KEEPER_CONFIG_UPDATE= -# number of chainlink nodes with keeper job to run, min 2 +# Optional Keepers config +KEEPER_REGISTRY_ADDRESS= +KEEPER_CONFIG_UPDATE= +# local node creation via docker: number of chainlink nodes with keeper job to run, min 2 KEEPERS_COUNT=2 + +# UpKeep Config UPKEEP_TEST_RANGE=1 -UPKEEP_AVERAGE_ELIGIBILITY_CADENCE=1 +UPKEEP_INTERVAL=10 # this will deploy UpkeepCounter, defaults to 10. +UPKEEP_AVERAGE_ELIGIBILITY_CADENCE=1 # this will deploy UpkeepPerformCounterRestrictive UPKEEP_CHECK_DATA=0x00 UPKEEP_GAS_LIMIT= UPKEEP_COUNT=5 diff --git a/core/scripts/chaincli/command/keeper/registry.go b/core/scripts/chaincli/command/keeper/registry.go index 7b15f92a0b7..32ff1d42ee3 100644 --- a/core/scripts/chaincli/command/keeper/registry.go +++ b/core/scripts/chaincli/command/keeper/registry.go @@ -18,3 +18,16 @@ var updateRegistryCmd = &cobra.Command{ hdlr.GetRegistry(cmd.Context()) }, } + +var withdrawFromRegistryCmd = &cobra.Command{ + Use: "withdraw", + Short: "cancel upkeeps and withdraw funds from registry", + Long: `This command will cancel all registered upkeeps and withdraw the funds left. args = Registry address`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.New() + hdlr := handler.NewKeeper(cfg) + + hdlr.Withdraw(cmd.Context(), args[0]) + }, +} diff --git a/core/scripts/chaincli/command/keeper/root.go b/core/scripts/chaincli/command/keeper/root.go index 5523a2249e6..7a05c1b2426 100644 --- a/core/scripts/chaincli/command/keeper/root.go +++ b/core/scripts/chaincli/command/keeper/root.go @@ -14,5 +14,8 @@ var RootCmd = &cobra.Command{ func init() { RootCmd.AddCommand(deployCmd) RootCmd.AddCommand(updateRegistryCmd) + RootCmd.AddCommand(withdrawFromRegistryCmd) RootCmd.AddCommand(launchAndTestCmd) + RootCmd.AddCommand(upkeepEventsCmd) + RootCmd.AddCommand(upkeepHistoryCmd) } diff --git a/core/scripts/chaincli/command/keeper/upkeep.go b/core/scripts/chaincli/command/keeper/upkeep.go new file mode 100644 index 00000000000..b1f3401fa38 --- /dev/null +++ b/core/scripts/chaincli/command/keeper/upkeep.go @@ -0,0 +1,71 @@ +package keeper + +import ( + "log" + "strconv" + + "github.com/spf13/cobra" + + "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" + "github.com/smartcontractkit/chainlink/core/scripts/chaincli/handler" +) + +// upkeepEventsCmd represents the command to run the upkeep events counter command +var upkeepEventsCmd = &cobra.Command{ + Use: "upkeep-events", + Short: "Print upkeep perform events(stdout and csv file)", + Long: `Print upkeep perform events and write to a csv file. args = hexaddr, fromBlock, toBlock`, + Run: func(cmd *cobra.Command, args []string) { + cfg := config.New() + hdlr := handler.NewKeeper(cfg) + fromBlock, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + log.Fatal(err) + } + toBlock, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + log.Fatal(err) + } + hdlr.UpkeepCounterEvents(cmd.Context(), args[0], fromBlock, toBlock) + }, +} + +// upkeepHistoryCmd represents the command to run the upkeep history command +var upkeepHistoryCmd = &cobra.Command{ + Use: "upkeep-history", + Short: "Print checkUpkeep history", + Long: `Print checkUpkeep status and keeper responsibility for a given upkeep in a set block range`, + Run: func(cmd *cobra.Command, args []string) { + upkeepId, err := cmd.Flags().GetInt64("upkeep-id") + if err != nil { + log.Fatal("failed to get 'upkeep-id' flag: ", err) + } + + fromBlock, err := cmd.Flags().GetUint64("from") + if err != nil { + log.Fatal("failed to get 'from' flag: ", err) + } + + toBlock, err := cmd.Flags().GetUint64("to") + if err != nil { + log.Fatal("failed to get 'to' flag: ", err) + } + + gasPrice, err := cmd.Flags().GetUint64("gas-price") + if err != nil { + log.Fatal("failed to get 'gas-price' flag: ", err) + } + + cfg := config.New() + hdlr := handler.NewKeeper(cfg) + + hdlr.UpkeepHistory(cmd.Context(), upkeepId, fromBlock, toBlock, gasPrice) + }, +} + +func init() { + upkeepHistoryCmd.Flags().Int64("upkeep-id", 0, "upkeep ID") + upkeepHistoryCmd.Flags().Uint64("from", 0, "from block") + upkeepHistoryCmd.Flags().Uint64("to", 0, "to block") + upkeepHistoryCmd.Flags().Uint64("gas-price", 0, "gas price to use") +} diff --git a/core/scripts/chaincli/config/config.go b/core/scripts/chaincli/config/config.go index 05f805fcd28..8251a3149cc 100644 --- a/core/scripts/chaincli/config/config.go +++ b/core/scripts/chaincli/config/config.go @@ -8,14 +8,18 @@ import ( // Config represents configuration fields type Config struct { - NodeURL string `mapstructure:"NODE_URL"` - ChainID int64 `mapstructure:"CHAIN_ID"` - PrivateKey string `mapstructure:"PRIVATE_KEY"` - LinkTokenAddr string `mapstructure:"LINK_TOKEN_ADDR"` - Keepers []string `mapstructure:"KEEPERS"` - ApproveAmount string `mapstructure:"APPROVE_AMOUNT"` - GasLimit uint64 `mapstructure:"GAS_LIMIT"` - FundNodeAmount int `mapstructure:"FUND_CHAINLINK_NODE"` + NodeURL string `mapstructure:"NODE_URL"` + ChainID int64 `mapstructure:"CHAIN_ID"` + PrivateKey string `mapstructure:"PRIVATE_KEY"` + LinkTokenAddr string `mapstructure:"LINK_TOKEN_ADDR"` + Keepers []string `mapstructure:"KEEPERS"` + KeeperURLs []string `mapstructure:"KEEPER_URLS"` + KeeperEmails []string `mapstructure:"KEEPER_EMAILS"` + KeeperPasswords []string `mapstructure:"KEEPER_PASSWORDS"` + ApproveAmount string `mapstructure:"APPROVE_AMOUNT"` + GasLimit uint64 `mapstructure:"GAS_LIMIT"` + FundNodeAmount int `mapstructure:"FUND_CHAINLINK_NODE"` + MustTakeTurns bool `mapstructure:"MUST_TAKE_TURNS"` // Keeper config LinkETHFeedAddr string `mapstructure:"LINK_ETH_FEED"` @@ -35,6 +39,7 @@ type Config struct { KeepersCount int `mapstructure:"KEEPERS_COUNT"` UpkeepTestRange int64 `mapstructure:"UPKEEP_TEST_RANGE"` UpkeepAverageEligibilityCadence int64 `mapstructure:"UPKEEP_AVERAGE_ELIGIBILITY_CADENCE"` + UpkeepInterval int64 `mapstructure:"UPKEEP_INTERVAL"` UpkeepCheckData string `mapstructure:"UPKEEP_CHECK_DATA"` UpkeepGasLimit uint32 `mapstructure:"UPKEEP_GAS_LIMIT"` UpkeepCount int64 `mapstructure:"UPKEEP_COUNT"` @@ -84,11 +89,12 @@ func init() { // Represented in WEI, which is 100 Ether viper.SetDefault("UPKEEP_ADD_FUNDS_AMOUNT", "100000000000000000000") viper.SetDefault("UPKEEP_TEST_RANGE", 1) - viper.SetDefault("UPKEEP_AVERAGE_ELIGIBILITY_CADENCE", 1) + viper.SetDefault("UPKEEP_INTERVAL", 10) viper.SetDefault("UPKEEP_CHECK_DATA", "0x00") viper.SetDefault("UPKEEP_GAS_LIMIT", 500000) viper.SetDefault("UPKEEP_COUNT", 5) viper.SetDefault("KEEPERS_COUNT", 2) viper.SetDefault("FEED_DECIMALS", 18) + viper.SetDefault("MUST_TAKE_TURNS", true) } diff --git a/core/scripts/chaincli/handler/handler.go b/core/scripts/chaincli/handler/handler.go index b85966a16f4..f65f7b38f73 100644 --- a/core/scripts/chaincli/handler/handler.go +++ b/core/scripts/chaincli/handler/handler.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" link "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" @@ -21,6 +22,7 @@ import ( type baseHandler struct { cfg *config.Config + rpcClient *rpc.Client client *ethclient.Client privateKey *ecdsa.PrivateKey linkToken *link.LinkToken @@ -31,10 +33,11 @@ type baseHandler struct { // NewBaseHandler is the constructor of baseHandler func NewBaseHandler(cfg *config.Config) *baseHandler { // Created a client by the given node address - nodeClient, err := ethclient.Dial(cfg.NodeURL) + rpcClient, err := rpc.Dial(cfg.NodeURL) if err != nil { log.Fatal("failed to deal with ETH node", err) } + nodeClient := ethclient.NewClient(rpcClient) // Parse private key d := new(big.Int).SetBytes(common.FromHex(cfg.PrivateKey)) @@ -68,6 +71,7 @@ func NewBaseHandler(cfg *config.Config) *baseHandler { return &baseHandler{ cfg: cfg, client: nodeClient, + rpcClient: rpcClient, privateKey: privateKey, linkToken: linkToken, fromAddr: fromAddr, diff --git a/core/scripts/chaincli/handler/keeper.go b/core/scripts/chaincli/handler/keeper.go index 3bbc8df8460..1d0271fd164 100644 --- a/core/scripts/chaincli/handler/keeper.go +++ b/core/scripts/chaincli/handler/keeper.go @@ -8,11 +8,16 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" - keeper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_wrapper" + "github.com/smartcontractkit/chainlink/core/cmd" + keeper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/upkeep_counter_wrapper" upkeep "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" + "github.com/smartcontractkit/chainlink/core/sessions" ) // Keeper is the keepers commands handler @@ -40,7 +45,7 @@ func (k *Keeper) DeployKeepers(ctx context.Context) { } func (k *Keeper) deployKeepers(ctx context.Context, keepers []common.Address, owners []common.Address) common.Address { - var registry *keeper.KeeperRegistry + var registry *keeper.KeeperRegistryVB var registryAddr common.Address var upkeepCount int64 if k.cfg.RegistryAddress != "" { @@ -62,6 +67,18 @@ func (k *Keeper) deployKeepers(ctx context.Context, keepers []common.Address, ow upkeepCount = 0 } + // Create Keeper Jobs on Nodes for Registry + for i, keeperAddr := range k.cfg.Keepers { + url := k.cfg.KeeperURLs[i] + email := k.cfg.KeeperEmails[i] + pwd := k.cfg.KeeperPasswords[i] + err := k.createKeeperJobOnExistingNode(url, email, pwd, registryAddr.Hex(), keeperAddr) + if err != nil { + log.Printf("Keeper Job not created for keeper %d: %s %s\n", i, url, keeperAddr) + log.Println("Please create it manually") + } + } + // Approve keeper registry approveRegistryTx, err := k.linkToken.Approve(k.buildTxOpts(ctx), registryAddr, k.approveAmount) if err != nil { @@ -85,8 +102,8 @@ func (k *Keeper) deployKeepers(ctx context.Context, keepers []common.Address, ow return registryAddr } -func (k *Keeper) deployRegistry(ctx context.Context) (common.Address, *keeper.KeeperRegistry) { - registryAddr, deployKeeperRegistryTx, registryInstance, err := keeper.DeployKeeperRegistry(k.buildTxOpts(ctx), k.client, +func (k *Keeper) deployRegistry(ctx context.Context) (common.Address, *keeper.KeeperRegistryVB) { + registryAddr, deployKeeperRegistryTx, registryInstance, err := keeper.DeployKeeperRegistryVB(k.buildTxOpts(ctx), k.client, common.HexToAddress(k.cfg.LinkTokenAddr), common.HexToAddress(k.cfg.LinkETHFeedAddr), common.HexToAddress(k.cfg.FastGasFeedAddr), @@ -98,6 +115,7 @@ func (k *Keeper) deployRegistry(ctx context.Context) (common.Address, *keeper.Ke k.cfg.GasCeilingMultiplier, big.NewInt(k.cfg.FallbackGasPrice), big.NewInt(k.cfg.FallbackLinkPrice), + k.cfg.MustTakeTurns, ) if err != nil { log.Fatal("DeployAbi failed: ", err) @@ -108,9 +126,9 @@ func (k *Keeper) deployRegistry(ctx context.Context) (common.Address, *keeper.Ke } // GetRegistry is used to attach to an existing registry -func (k *Keeper) GetRegistry(ctx context.Context) (common.Address, *keeper.KeeperRegistry) { +func (k *Keeper) GetRegistry(ctx context.Context) (common.Address, *keeper.KeeperRegistryVB) { registryAddr := common.HexToAddress(k.cfg.RegistryAddress) - registryInstance, err := keeper.NewKeeperRegistry( + registryInstance, err := keeper.NewKeeperRegistryVB( registryAddr, k.client, ) @@ -127,7 +145,8 @@ func (k *Keeper) GetRegistry(ctx context.Context) (common.Address, *keeper.Keepe big.NewInt(k.cfg.StalenessSeconds), k.cfg.GasCeilingMultiplier, big.NewInt(k.cfg.FallbackGasPrice), - big.NewInt(k.cfg.FallbackLinkPrice)) + big.NewInt(k.cfg.FallbackLinkPrice), + k.cfg.MustTakeTurns) if err != nil { log.Fatal("Registry config update: ", err) } @@ -140,17 +159,26 @@ func (k *Keeper) GetRegistry(ctx context.Context) (common.Address, *keeper.Keepe } // deployUpkeeps deploys N amount of upkeeps and register them in the keeper registry deployed above -func (k *Keeper) deployUpkeeps(ctx context.Context, registryAddr common.Address, registryInstance *keeper.KeeperRegistry, existingCount int64) { +func (k *Keeper) deployUpkeeps(ctx context.Context, registryAddr common.Address, registryInstance *keeper.KeeperRegistryVB, existingCount int64) { fmt.Println() log.Println("Deploying upkeeps...") for i := existingCount; i < k.cfg.UpkeepCount+existingCount; i++ { fmt.Println() // Deploy - upkeepAddr, deployUpkeepTx, _, err := upkeep.DeployUpkeepPerformCounterRestrictive(k.buildTxOpts(ctx), k.client, - big.NewInt(k.cfg.UpkeepTestRange), big.NewInt(k.cfg.UpkeepAverageEligibilityCadence), - ) + var upkeepAddr common.Address + var deployUpkeepTx *types.Transaction + var err error + if k.cfg.UpkeepAverageEligibilityCadence > 0 { + upkeepAddr, deployUpkeepTx, _, err = upkeep.DeployUpkeepPerformCounterRestrictive(k.buildTxOpts(ctx), k.client, + big.NewInt(k.cfg.UpkeepTestRange), big.NewInt(k.cfg.UpkeepAverageEligibilityCadence), + ) + } else { + upkeepAddr, deployUpkeepTx, _, err = upkeep_counter_wrapper.DeployUpkeepCounter(k.buildTxOpts(ctx), k.client, + big.NewInt(k.cfg.UpkeepTestRange), big.NewInt(k.cfg.UpkeepInterval), + ) + } if err != nil { - log.Fatal(i, ": DeployAbi failed - ", err) + log.Fatal(i, ": Deploy Upkeep failed - ", err) } k.waitDeployment(ctx, deployUpkeepTx) log.Println(i, upkeepAddr.Hex(), ": Upkeep deployed - ", helpers.ExplorerLink(k.cfg.ChainID, deployUpkeepTx.Hash())) @@ -193,3 +221,24 @@ func (k *Keeper) keepers() ([]common.Address, []common.Address) { } return addrs, fromAddrs } + +// createKeeperJobOnExistingNode connect to existing node to create keeper job +func (k *Keeper) createKeeperJobOnExistingNode(url, email, password, registryAddr, nodeAddr string) error { + c := cfg{nodeURL: url} + sr := sessions.SessionRequest{Email: email, Password: password} + store := &cmd.MemoryCookieStore{} + lggr, close := logger.NewLogger() + defer close() + tca := cmd.NewSessionCookieAuthenticator(c, store, lggr) + if _, err := tca.Authenticate(sr); err != nil { + log.Println("failed to authenticate: ", err) + return err + } + cl := cmd.NewAuthenticatedHTTPClient(c, tca, sr) + + if err := k.createKeeperJob(cl, registryAddr, nodeAddr); err != nil { + log.Println("Failed to create keeper job: ", err) + return err + } + return nil +} diff --git a/core/scripts/chaincli/handler/keeper_launch.go b/core/scripts/chaincli/handler/keeper_launch.go index e4bbf757bc2..888ca0639fd 100644 --- a/core/scripts/chaincli/handler/keeper_launch.go +++ b/core/scripts/chaincli/handler/keeper_launch.go @@ -27,7 +27,7 @@ import ( "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink/core/cmd" - keeper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_wrapper" + keeper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper" "github.com/smartcontractkit/chainlink/core/logger" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" "github.com/smartcontractkit/chainlink/core/sessions" @@ -86,7 +86,7 @@ func (k *Keeper) LaunchAndTest(ctx context.Context, withdraw bool) { wg.Wait() // Deploy keeper registry or get an existing one - var registry *keeper.KeeperRegistry + var registry *keeper.KeeperRegistryVB var registryAddr common.Address var upkeepCount int64 if k.cfg.RegistryAddress != "" { @@ -119,6 +119,9 @@ func (k *Keeper) LaunchAndTest(ctx context.Context, withdraw bool) { // Deploy Upkeeps k.deployUpkeeps(ctx, registryAddr, registry, upkeepCount) + lggr, closeLggr := logger.NewLogger() + defer closeLggr() + // Prepare keeper addresses and owners var keepers []common.Address var owners []common.Address @@ -132,7 +135,7 @@ func (k *Keeper) LaunchAndTest(ctx context.Context, withdraw bool) { c := cfg{nodeURL: startedNode.url} sr := sessions.SessionRequest{Email: defaultChainlinkNodeLogin, Password: defaultChainlinkNodePassword} store := &cmd.MemoryCookieStore{} - tca := cmd.NewSessionCookieAuthenticator(c, store, logger.NewLogger()) + tca := cmd.NewSessionCookieAuthenticator(c, store, lggr) if _, err = tca.Authenticate(sr); err != nil { log.Println("failed to authenticate: ", err) continue @@ -198,7 +201,7 @@ func (k *Keeper) LaunchAndTest(ctx context.Context, withdraw bool) { } // cancelAndWithdrawUpkeeps cancels all upkeeps of the registry and withdraws funds -func (k *Keeper) cancelAndWithdrawUpkeeps(ctx context.Context, registryInstance *keeper.KeeperRegistry) error { +func (k *Keeper) cancelAndWithdrawUpkeeps(ctx context.Context, registryInstance *keeper.KeeperRegistryVB) error { count, err := registryInstance.GetUpkeepCount(&bind.CallOpts{Context: ctx}) if err != nil { return fmt.Errorf("failed to get upkeeps count: %s", err) @@ -250,10 +253,11 @@ func (k *Keeper) getNodeAddress(client cmd.HTTPClient) (string, error) { } // createKeeperJob creates a keeper job in the chainlink node by the given address -func (k *Keeper) createKeeperJob(client cmd.HTTPClient, contractAddr, nodeAddr string) error { +func (k *Keeper) createKeeperJob(client cmd.HTTPClient, registryAddr, nodeAddr string) error { request, err := json.Marshal(web.CreateJobRequest{ TOML: testspecs.GenerateKeeperSpec(testspecs.KeeperSpecParams{ - ContractAddress: contractAddr, + Name: fmt.Sprintf("keeper job - registry %s", registryAddr), + ContractAddress: registryAddr, FromAddress: nodeAddr, EvmChainID: int(k.cfg.ChainID), MinIncomingConfirmations: 1, @@ -277,9 +281,7 @@ func (k *Keeper) createKeeperJob(client cmd.HTTPClient, contractAddr, nodeAddr s return fmt.Errorf("unable to create keeper job: '%v' [%d]", string(body), resp.StatusCode) } - - log.Println("Keeper job has been successfully created") - + log.Println("Keeper job has been successfully created in the Chainlink node with address: ", nodeAddr) return nil } diff --git a/core/scripts/chaincli/handler/keeper_upkeep_events.go b/core/scripts/chaincli/handler/keeper_upkeep_events.go new file mode 100644 index 00000000000..22e1b659f68 --- /dev/null +++ b/core/scripts/chaincli/handler/keeper_upkeep_events.go @@ -0,0 +1,65 @@ +package handler + +import ( + "context" + "encoding/csv" + "fmt" + "log" + "os" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/upkeep_counter_wrapper" +) + +// UpkeepCounterEvents print out emitted events and write to csv file +func (k *Keeper) UpkeepCounterEvents(ctx context.Context, hexAddr string, fromBlock, toBlock uint64) { + contractAddress := common.HexToAddress(hexAddr) + upkeepCounter, err := upkeep_counter_wrapper.NewUpkeepCounter(contractAddress, k.client) + if err != nil { + log.Fatalln("Failed to create a new upkeep counter", err) + } + filterOpts := bind.FilterOpts{ + Start: fromBlock, + End: &toBlock, + Context: ctx, + } + upkeepIterator, err := upkeepCounter.FilterPerformingUpkeep(&filterOpts, nil) + if err != nil { + log.Fatalln("Failed to get upkeep iterator", err) + } + filename := fmt.Sprintf("%s.csv", hexAddr) + file, err := os.Create(filename) + if err != nil { + log.Fatalln("failed to open file", err) + } + defer file.Close() + + w := csv.NewWriter(file) + defer w.Flush() + + fmt.Println("From, InitialBlock, LastBlock, PreviousBlock, Counter") + row := []string{"From", "InitialBlock", "LastBlock", "PreviousBlock", "Counter"} + if err := w.Write(row); err != nil { + log.Fatalln("error writing record to file", err) + } + + for upkeepIterator.Next() { + fmt.Printf("%s,%s,%s,%s,%s\n", + upkeepIterator.Event.From, + upkeepIterator.Event.InitialBlock, + upkeepIterator.Event.LastBlock, + upkeepIterator.Event.PreviousBlock, + upkeepIterator.Event.Counter, + ) + row := []string{upkeepIterator.Event.From.String(), + upkeepIterator.Event.InitialBlock.String(), + upkeepIterator.Event.LastBlock.String(), + upkeepIterator.Event.PreviousBlock.String(), + upkeepIterator.Event.Counter.String()} + if err := w.Write(row); err != nil { + log.Fatalln("error writing record to file", err) + } + } +} diff --git a/core/scripts/chaincli/handler/keeper_upkeep_history.go b/core/scripts/chaincli/handler/keeper_upkeep_history.go new file mode 100644 index 00000000000..252180e9c87 --- /dev/null +++ b/core/scripts/chaincli/handler/keeper_upkeep_history.go @@ -0,0 +1,172 @@ +package handler + +import ( + "context" + "encoding/hex" + "fmt" + "log" + "math/big" + "os" + "strings" + "text/tabwriter" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/smartcontractkit/chainlink/core/services/keeper" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" +) + +const ( + defaultMaxBlocksRange = 1000 +) + +var ( + checkUpkeepArguments abi.Arguments +) + +func init() { + checkUpkeepArguments = keeper.RegistryABI.Methods["checkUpkeep"].Outputs +} + +// UpkeepHistory prints the checkUpkeep status and keeper responsibility for a given upkeep in a set block range +func (k *Keeper) UpkeepHistory(ctx context.Context, upkeepId int64, from, to, gasPrice uint64) { + // There must not be a large different between boundaries + if to-from > defaultMaxBlocksRange { + log.Fatalf("blocks range difference must not more than %d", defaultMaxBlocksRange) + } + + registryAddr, registryClient := k.GetRegistry(ctx) + + // Get positioning constant of the current registry + positioningConstant, err := keeper.CalcPositioningConstant(upkeepId, ethkey.EIP55AddressFromAddress(registryAddr)) + if err != nil { + log.Fatal("failed to get positioning constant: ", err) + } + log.Println("Calculated Positioning Constant for the registry: ", positioningConstant) + + log.Println("Preparing a batch call request") + var reqs []rpc.BatchElem + var results []*string + var keeperPerBlockIndex []uint64 + var keeperPerBlockAddress []common.Address + for block := from; block <= to; block++ { + callOpts := bind.CallOpts{ + Context: ctx, + BlockNumber: big.NewInt(0).SetUint64(block), + } + + registryConfig, err := registryClient.GetConfig(&callOpts) + if err != nil { + log.Fatal("failed to fetch registry config: ", err) + } + blockCountPerTurn := registryConfig.BlockCountPerTurn.Uint64() + + keepersList, err := registryClient.GetKeeperList(&callOpts) + if err != nil { + log.Fatal("failed to fetch keepers list: ", err) + } + + keeperIndex := (uint64(positioningConstant) + ((block - (block % blockCountPerTurn)) / blockCountPerTurn)) % uint64(len(keepersList)) + payload, err := keeper.RegistryABI.Pack("checkUpkeep", big.NewInt(upkeepId), keepersList[keeperIndex]) + if err != nil { + log.Fatal("failed to pack checkUpkeep: ", err) + } + + args := map[string]interface{}{ + "to": k.cfg.RegistryAddress, + "data": hexutil.Bytes(payload), + } + if gasPrice > 0 { + args["gasPrice"] = hexutil.EncodeUint64(gasPrice) + } + + var result string + reqs = append(reqs, rpc.BatchElem{ + Method: "eth_call", + Args: []interface{}{ + args, + // The block at which we want to inspect the upkeep state + hexutil.EncodeUint64(block), + }, + Result: &result, + }) + + results = append(results, &result) + keeperPerBlockIndex = append(keeperPerBlockIndex, keeperIndex) + keeperPerBlockAddress = append(keeperPerBlockAddress, keepersList[keeperIndex]) + } + + log.Println("Doing batch call to check upkeeps") + if err := k.rpcClient.BatchCallContext(ctx, reqs); err != nil { + log.Fatal("failed to batch call checkUpkeep: ", err) + } + + log.Println("Parsing batch call response") + type result struct { + block uint64 + checkUpkeep bool + keeperIndex uint64 + keeperAddress common.Address + reason string + performData string + maxLinkPayment *big.Int + gasLimit *big.Int + adjustedGasWei *big.Int + linkEth *big.Int + } + var parsedResults []result + for i, req := range reqs { + if req.Error != nil { + parsedResults = append(parsedResults, result{ + block: uint64(i) + from, + checkUpkeep: false, + keeperIndex: keeperPerBlockIndex[i], + keeperAddress: keeperPerBlockAddress[i], + reason: strings.TrimPrefix(req.Error.Error(), "execution reverted: "), + }) + continue + } + + returnValues, err := checkUpkeepArguments.UnpackValues(hexutil.MustDecode(*results[i])) + if err != nil { + log.Fatal("unpack checkUpkeep return: ", err, *results[i]) + } + + parsedResults = append(parsedResults, result{ + block: uint64(i) + from, + checkUpkeep: true, + keeperIndex: keeperPerBlockIndex[i], + keeperAddress: keeperPerBlockAddress[i], + performData: "0x" + hex.EncodeToString(*abi.ConvertType(returnValues[0], new([]byte)).(*[]byte)), + maxLinkPayment: *abi.ConvertType(returnValues[1], new(*big.Int)).(**big.Int), + gasLimit: *abi.ConvertType(returnValues[2], new(*big.Int)).(**big.Int), + adjustedGasWei: *abi.ConvertType(returnValues[3], new(*big.Int)).(**big.Int), + linkEth: *abi.ConvertType(returnValues[4], new(*big.Int)).(**big.Int), + }) + } + + writer := tabwriter.NewWriter(os.Stdout, 8, 8, 0, '\t', 0) + defer writer.Flush() + + fmt.Fprintf(writer, "\n %s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t", "Block", "checkUpkeep", "Keeper Index", "Keeper Address", "Max LINK Payment", "Gas Limit", "Adjusted Gas", "LINK ETH", "Perform Data", "Reason") + fmt.Fprintf(writer, "\n %s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t", "----", "----", "----", "----", "----", "----", "----", "----", "----", "----") + for _, res := range parsedResults { + fmt.Fprintf(writer, "\n %d\t\t%t\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t", + res.block, + res.checkUpkeep, + res.keeperIndex, + res.keeperAddress, + res.maxLinkPayment, + res.gasLimit, + res.adjustedGasWei, + res.linkEth, + res.performData, + res.reason, + ) + } + fmt.Fprintf(writer, "\n %s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "----", "----", "----", "----", "----", "----", "----", "----", "----", "----") +} diff --git a/core/scripts/chaincli/handler/keeper_withdraw.go b/core/scripts/chaincli/handler/keeper_withdraw.go new file mode 100644 index 00000000000..87582cd5568 --- /dev/null +++ b/core/scripts/chaincli/handler/keeper_withdraw.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "log" + + "github.com/ethereum/go-ethereum/common" + + keeper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_vb_wrapper" +) + +// Withdraw takes a keeper registry address cancels all upkeeps and withdraws the funds +func (k *Keeper) Withdraw(ctx context.Context, hexAddr string) { + registryAddr := common.HexToAddress(hexAddr) + registryInstance, err := keeper.NewKeeperRegistryVB( + registryAddr, + k.client, + ) + if err != nil { + log.Fatal("Registry failed: ", err) + } + log.Println("Canceling upkeeps...") + if err := k.cancelAndWithdrawUpkeeps(ctx, registryInstance); err != nil { + log.Fatal("Failed to cancel upkeeps: ", err) + } + log.Println("Upkeeps successfully canceled") +} diff --git a/core/scripts/vrfv1/main.go b/core/scripts/vrfv1/main.go index 122b5d7aac2..bbca2d65d0d 100644 --- a/core/scripts/vrfv1/main.go +++ b/core/scripts/vrfv1/main.go @@ -14,8 +14,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/shopspring/decimal" linktoken "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" + vrfltoc "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_load_test_ownerless_consumer" vrfoc "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_ownerless_consumer_example" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" "github.com/smartcontractkit/chainlink/core/utils" @@ -70,6 +72,17 @@ func main() { helpers.PanicErr(err) account.GasPrice = gp + // Uncomment the block below if transactions are not getting picked up due to nonce issues: + // + //block, err := ec.BlockNumber(context.Background()) + //helpers.PanicErr(err) + // + //nonce, err := ec.NonceAt(context.Background(), account.From, big.NewInt(int64(block))) + //helpers.PanicErr(err) + // + //account.Nonce = big.NewInt(int64(nonce)) + //account.GasPrice = gp.Mul(gp, big.NewInt(2)) + switch os.Args[1] { case "ownerless-consumer-deploy": cmd := flag.NewFlagSet("ownerless-consumer-deploy", flag.ExitOnError) @@ -84,6 +97,22 @@ func main() { helpers.PanicErr(err) fmt.Printf("Ownerless Consumer: %s TX: %s\n", consumerAddr, helpers.ExplorerLink(chainID, tx.Hash())) + case "loadtest-ownerless-consumer-deploy": + cmd := flag.NewFlagSet("loadtest-ownerless-consumer-deploy", flag.ExitOnError) + coordAddr := cmd.String("coordinator-address", "", "address of VRF coordinator") + linkAddr := cmd.String("link-address", "", "address of link token") + priceStr := cmd.String("price", "", "the price of each VRF request in Juels") + helpers.ParseArgs(cmd, os.Args[2:], "coordinator-address", "link-address") + price := decimal.RequireFromString(*priceStr).BigInt() + consumerAddr, tx, _, err := vrfltoc.DeployVRFLoadTestOwnerlessConsumer( + account, + ec, + common.HexToAddress(*coordAddr), + common.HexToAddress(*linkAddr), + price) + helpers.PanicErr(err) + fmt.Printf("Loadtest Ownerless Consumer: %s TX: %s\n", + consumerAddr, helpers.ExplorerLink(chainID, tx.Hash())) case "ownerless-consumer-request": cmd := flag.NewFlagSet("ownerless-consumer-request", flag.ExitOnError) linkAddr := cmd.String("link-address", "", "address of link token") diff --git a/core/scripts/vrfv2/testnet/README.md b/core/scripts/vrfv2/testnet/README.md index 706833a7254..63a127ceb08 100644 --- a/core/scripts/vrfv2/testnet/README.md +++ b/core/scripts/vrfv2/testnet/README.md @@ -154,3 +154,81 @@ insufficient funds / LINK, or incorrect contract addresses. why a transaction failed. For example [this Rinkeby transaction](https://dashboard.tenderly.co/tx/rinkeby/0x71a7279033b47472ca453f7a19ccb685d0f32cdb4854a45052f1aaccd80436e9) failed because a non-owner tried to request random words from [VRFExternalSubOwnerExample](../../../../contracts/src/v0.8/tests/VRFExternalSubOwnerExample.sol). + +## Using the `BatchBlockhashStore` Contract + +The `BatchBlockhashStore` contract acts as a proxy to the `BlockhashStore` contract, allowing callers to store +and fetch many blockhashes in a single transaction. + +### Deploy a `BatchBlockhashStore` instance + +``` +go run main.go batch-bhs-deploy -bhs-address $BHS_ADDRESS +``` + +where `$BHS_ADDRESS` is an environment variable that points to an existing `BlockhashStore` contract. If one is not available, +you can easily deploy one using this command: + +``` +go run main.go bhs-deploy +``` + +### Store many blockhashes + +``` +go run main.go batch-bhs-store -batch-bhs-address $BATCH_BHS_ADDRESS -block-numbers 10298742,10298741,10298740,10298739 +``` + +where `$BATCH_BHS_ADDRESS` points to the `BatchBlockhashStore` contract deployed above, and `-block-numbers` is a comma-separated +list of block numbers you want to store in a single transaction. + +Please note that these block numbers must not be further than 256 from the latest head, otherwise the store will fail. + +### Fetch many blockhashes + +``` +go run main.go batch-bhs-get -batch-bhs-address $BATCH_BHS_ADDRESS -block-numbers 10298742,10298741,10298740,10298739 +``` + +where `$BATCH_BHS_ADDRESS` points to the `BatchBlockhashStore` contract deployed above, and `-block-numbers` is a comma-separated +list of block numbers you want to get in a single transaction. + +### Store many blockhashes, possibly farther back than 256 blocks + +In order to store blockhashes farther back than 256 blocks we can make use of the `storeVerifyHeader` method on the `BatchBlockhashStore`. + +Here's how to use it: + +``` +go run main.go batch-bhs-storeVerify -batch-bhs-address $BATCH_BHS_ADDRESS -num-blocks 25 -start-block 10298739 +``` + +where `$BATCH_BHS_ADDRESS` points to the `BatchBlockhashStore` contract deployed above, `-num-blocks` is the amount of blocks to store, and +`-start-block` is the block to start storing from, backwards. The block number specified by `-start-block` MUST be +in the blockhash store already, or this will not work. + +### Batch BHS "Backwards Mode" + +There may be a situation where you want to backfill a lot of blockhashes, down to a certain block number. + +This is where "Backwrads Mode" comes in - you're going to need the following: + +* A block number that has already been stored in the BHS. The closer it is to the target block range you want to store, +the better. You can view the most oldest "Store" transactions on the BHS contract that is still ahead of the block range you +are interested in. For example, if you want to store blocks 100 to 200, and 210 and 220 are available, specify `-start-block` +as `210`. +* A destination block number, where you want to stop storing after this one has been stored in the BHS. This number doesn't have +to be in the BHS already but must be less than the block specified for `--start-block` +* A batch size to use. This is how many stores we will attempt to do in a single transaction. A good value for this is usually 50-75 +for big block ranges. +* The address of the batch BHS to use. + +Example: + +``` +go run main.go batch-bhs-backwards -batch-bhs-address $BATCH_BHS_ADDRESS -start-block 25814538 -end-block 25811350 -batch-size 50 +``` + +This script is simplistic on purpose, where we wait for the transaction to mine before proceeding with the next one. This +is to avoid issues where a transaction gets sent and not included on-chain, and subsequent calls to `storeVerifyHeader` will +fail. diff --git a/core/scripts/vrfv2/testnet/main.go b/core/scripts/vrfv2/testnet/main.go index 63992371993..a63aa39bc1a 100644 --- a/core/scripts/vrfv2/testnet/main.go +++ b/core/scripts/vrfv2/testnet/main.go @@ -15,14 +15,17 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rlp" + "github.com/shopspring/decimal" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/blockhash_store" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_external_sub_owner_example" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_load_test_external_sub_owner" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_single_consumer_example" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" - "github.com/smartcontractkit/chainlink/core/services/vrf" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -72,7 +75,118 @@ func main() { gp, err := ec.SuggestGasPrice(context.Background()) helpers.PanicErr(err) owner.GasPrice = gp + + // Uncomment the block below if transactions are not getting picked up due to nonce issues: + // + //block, err := ec.BlockNumber(context.Background()) + //helpers.PanicErr(err) + // + //nonce, err := ec.NonceAt(context.Background(), owner.From, big.NewInt(int64(block))) + //helpers.PanicErr(err) + // + //owner.Nonce = big.NewInt(int64(nonce)) + //owner.GasPrice = gp.Mul(gp, big.NewInt(2)) + switch os.Args[1] { + case "batch-bhs-deploy": + cmd := flag.NewFlagSet("batch-bhs-deploy", flag.ExitOnError) + bhsAddr := cmd.String("bhs-address", "", "address of the blockhash store contract") + helpers.ParseArgs(cmd, os.Args[2:], "bhs-address") + batchBHSAddress, tx, _, err := batch_blockhash_store.DeployBatchBlockhashStore(owner, ec, common.HexToAddress(*bhsAddr)) + helpers.PanicErr(err) + fmt.Println("BatchBlockhashStore:", batchBHSAddress.Hex(), "tx:", helpers.ExplorerLink(chainID, tx.Hash())) + case "batch-bhs-store": + cmd := flag.NewFlagSet("batch-bhs-store", flag.ExitOnError) + batchAddr := cmd.String("batch-bhs-address", "", "address of the batch bhs contract") + blockNumbersArg := cmd.String("block-numbers", "", "block numbers to store in a single transaction") + helpers.ParseArgs(cmd, os.Args[2:], "batch-bhs-address", "block-numbers") + batchBHS, err := batch_blockhash_store.NewBatchBlockhashStore(common.HexToAddress(*batchAddr), ec) + helpers.PanicErr(err) + blockNumbers, err := parseIntSlice(*blockNumbersArg) + helpers.PanicErr(err) + tx, err := batchBHS.Store(owner, blockNumbers) + helpers.PanicErr(err) + fmt.Println("Store tx:", helpers.ExplorerLink(chainID, tx.Hash())) + case "batch-bhs-get": + cmd := flag.NewFlagSet("batch-bhs-get", flag.ExitOnError) + batchAddr := cmd.String("batch-bhs-address", "", "address of the batch bhs contract") + blockNumbersArg := cmd.String("block-numbers", "", "block numbers to store in a single transaction") + helpers.ParseArgs(cmd, os.Args[2:], "batch-bhs-address", "block-numbers") + batchBHS, err := batch_blockhash_store.NewBatchBlockhashStore(common.HexToAddress(*batchAddr), ec) + helpers.PanicErr(err) + blockNumbers, err := parseIntSlice(*blockNumbersArg) + helpers.PanicErr(err) + blockhashes, err := batchBHS.GetBlockhashes(nil, blockNumbers) + helpers.PanicErr(err) + for i, bh := range blockhashes { + fmt.Println("blockhash(", blockNumbers[i], ") = ", common.Bytes2Hex(bh[:])) + } + case "batch-bhs-storeVerify": + cmd := flag.NewFlagSet("batch-bhs-storeVerify", flag.ExitOnError) + batchAddr := cmd.String("batch-bhs-address", "", "address of the batch bhs contract") + startBlock := cmd.Int64("start-block", -1, "block number to start from. Must be in the BHS already.") + numBlocks := cmd.Int64("num-blocks", -1, "number of blockhashes to store. will be stored in a single tx, can't be > 150") + helpers.ParseArgs(cmd, os.Args[2:], "batch-bhs-address", "start-block", "num-blocks") + batchBHS, err := batch_blockhash_store.NewBatchBlockhashStore(common.HexToAddress(*batchAddr), ec) + helpers.PanicErr(err) + blockRange, err := decreasingBlockRange(big.NewInt(*startBlock-1), big.NewInt(*startBlock-*numBlocks-1)) + helpers.PanicErr(err) + rlpHeaders, err := getRlpHeaders(ec, blockRange) + helpers.PanicErr(err) + tx, err := batchBHS.StoreVerifyHeader(owner, blockRange, rlpHeaders) + helpers.PanicErr(err) + fmt.Println("storeVerifyHeader(", blockRange, ", ...) tx:", helpers.ExplorerLink(chainID, tx.Hash())) + case "batch-bhs-backwards": + cmd := flag.NewFlagSet("batch-bhs-backwards", flag.ExitOnError) + batchAddr := cmd.String("batch-bhs-address", "", "address of the batch bhs contract") + startBlock := cmd.Int64("start-block", -1, "block number to start from. Must be in the BHS already.") + endBlock := cmd.Int64("end-block", -1, "block number to end at. Must be less than startBlock") + batchSize := cmd.Int64("batch-size", -1, "batch size") + gasMultiplier := cmd.Int64("gas-price-multiplier", 1, "gas price multiplier to use, defaults to 1 (no multiplication)") + helpers.ParseArgs(cmd, os.Args[2:], "batch-bhs-address", "start-block", "end-block", "batch-size") + + batchBHS, err := batch_blockhash_store.NewBatchBlockhashStore(common.HexToAddress(*batchAddr), ec) + helpers.PanicErr(err) + + blockRange, err := decreasingBlockRange(big.NewInt(*startBlock-1), big.NewInt(*endBlock)) + helpers.PanicErr(err) + + for i := 0; i < len(blockRange); i += int(*batchSize) { + j := i + int(*batchSize) + if j > len(blockRange) { + j = len(blockRange) + } + + // Get suggested gas price and multiply by multiplier on every iteration + // so we don't have our transaction getting stuck. Need to be as fast as + // possible. + gp, err := ec.SuggestGasPrice(context.Background()) + helpers.PanicErr(err) + owner.GasPrice = new(big.Int).Mul(gp, big.NewInt(*gasMultiplier)) + + fmt.Println("using gas price", owner.GasPrice, "wei") + + blockNumbers := blockRange[i:j] + blockHeaders, err := getRlpHeaders(ec, blockNumbers) + fmt.Println("storing blockNumbers:", blockNumbers) + helpers.PanicErr(err) + + tx, err := batchBHS.StoreVerifyHeader(owner, blockNumbers, blockHeaders) + helpers.PanicErr(err) + + fmt.Println("sent tx:", helpers.ExplorerLink(chainID, tx.Hash())) + + fmt.Println("waiting for it to mine...") + _, err = bind.WaitMined(context.Background(), ec, tx) + helpers.PanicErr(err) + + fmt.Println("received receipt, continuing") + } + fmt.Println("done") + case "latest-head": + h, err := ec.HeaderByNumber(context.Background(), nil) + helpers.PanicErr(err) + fmt.Println("latest head number:", h.Number.String()) case "bhs-deploy": bhsAddress, tx, _, err := blockhash_store.DeployBlockhashStore(owner, ec) helpers.PanicErr(err) @@ -91,30 +205,61 @@ func main() { common.HexToAddress(*coordinatorDeployLinkEthFeedAddress)) helpers.PanicErr(err) fmt.Println("Coordinator", coordinatorAddress.String(), "TX", helpers.ExplorerLink(chainID, tx.Hash())) + case "coordinator-get-config": + cmd := flag.NewFlagSet("coordinator-get-config", flag.ExitOnError) + setConfigAddress := cmd.String("coordinator-address", "", "coordinator address") + helpers.ParseArgs(cmd, os.Args[2:], "address") + + coordinator, err := vrf_coordinator_v2.NewVRFCoordinatorV2(common.HexToAddress(*setConfigAddress), ec) + helpers.PanicErr(err) + + cfg, err := coordinator.GetConfig(nil) + helpers.PanicErr(err) + + feeConfig, err := coordinator.GetFeeConfig(nil) + helpers.PanicErr(err) + + fmt.Printf("config: %+v\n", cfg) + fmt.Printf("fee config: %+v\n", feeConfig) case "coordinator-set-config": - coordinatorSetConfigCmd := flag.NewFlagSet("coordinator-set-config", flag.ExitOnError) - setConfigAddress := coordinatorSetConfigCmd.String("address", "", "coordinator address") - // TODO: add config parameters as cli args here - helpers.PanicErr(coordinatorSetConfigCmd.Parse(os.Args[2:])) + cmd := flag.NewFlagSet("coordinator-set-config", flag.ExitOnError) + setConfigAddress := cmd.String("coordinator-address", "", "coordinator address") + minConfs := cmd.Int("min-confs", 3, "min confs") + maxGasLimit := cmd.Int64("max-gas-limit", 2.5e6, "max gas limit") + stalenessSeconds := cmd.Int64("staleness-seconds", 86400, "staleness in seconds") + gasAfterPayment := cmd.Int64("gas-after-payment", 33285, "gas after payment calculation") + fallbackWeiPerUnitLink := cmd.String("fallback-wei-per-unit-link", "", "fallback wei per unit link") + flatFeeTier1 := cmd.Int64("flat-fee-tier-1", 500, "flat fee tier 1") + flatFeeTier2 := cmd.Int64("flat-fee-tier-2", 500, "flat fee tier 2") + flatFeeTier3 := cmd.Int64("flat-fee-tier-3", 500, "flat fee tier 3") + flatFeeTier4 := cmd.Int64("flat-fee-tier-4", 500, "flat fee tier 4") + flatFeeTier5 := cmd.Int64("flat-fee-tier-5", 500, "flat fee tier 5") + reqsForTier2 := cmd.Int64("reqs-for-tier-2", 0, "requests for tier 2") + reqsForTier3 := cmd.Int64("reqs-for-tier-3", 0, "requests for tier 3") + reqsForTier4 := cmd.Int64("reqs-for-tier-4", 0, "requests for tier 4") + reqsForTier5 := cmd.Int64("reqs-for-tier-5", 0, "requests for tier 5") + + helpers.ParseArgs(cmd, os.Args[2:], "coordinator-address", "fallback-wei-per-unit-link") + coordinator, err := vrf_coordinator_v2.NewVRFCoordinatorV2(common.HexToAddress(*setConfigAddress), ec) helpers.PanicErr(err) - helpers.ParseArgs(coordinatorSetConfigCmd, os.Args[2:], "address") + tx, err := coordinator.SetConfig(owner, - uint16(1), // minRequestConfirmations - uint32(1000000), // max gas limit - uint32(60*60*24), // stalenessSeconds - uint32(vrf.GasAfterPaymentCalculation), // gasAfterPaymentCalculation - big.NewInt(10000000000000000), // 0.01 eth per link fallbackLinkPrice + uint16(*minConfs), // minRequestConfirmations + uint32(*maxGasLimit), // max gas limit + uint32(*stalenessSeconds), // stalenessSeconds + uint32(*gasAfterPayment), // gasAfterPaymentCalculation + decimal.RequireFromString(*fallbackWeiPerUnitLink).BigInt(), // 0.01 eth per link fallbackLinkPrice vrf_coordinator_v2.VRFCoordinatorV2FeeConfig{ - FulfillmentFlatFeeLinkPPMTier1: uint32(10000), - FulfillmentFlatFeeLinkPPMTier2: uint32(1000), - FulfillmentFlatFeeLinkPPMTier3: uint32(100), - FulfillmentFlatFeeLinkPPMTier4: uint32(10), - FulfillmentFlatFeeLinkPPMTier5: uint32(1), - ReqsForTier2: big.NewInt(10), - ReqsForTier3: big.NewInt(20), - ReqsForTier4: big.NewInt(30), - ReqsForTier5: big.NewInt(40), + FulfillmentFlatFeeLinkPPMTier1: uint32(*flatFeeTier1), + FulfillmentFlatFeeLinkPPMTier2: uint32(*flatFeeTier2), + FulfillmentFlatFeeLinkPPMTier3: uint32(*flatFeeTier3), + FulfillmentFlatFeeLinkPPMTier4: uint32(*flatFeeTier4), + FulfillmentFlatFeeLinkPPMTier5: uint32(*flatFeeTier5), + ReqsForTier2: big.NewInt(*reqsForTier2), + ReqsForTier3: big.NewInt(*reqsForTier3), + ReqsForTier4: big.NewInt(*reqsForTier4), + ReqsForTier5: big.NewInt(*reqsForTier5), }, ) helpers.PanicErr(err) @@ -279,6 +424,18 @@ func main() { common.HexToAddress(*consumerLinkAddress)) helpers.PanicErr(err) fmt.Println("Consumer address", consumerAddress, "TX", helpers.ExplorerLink(chainID, tx.Hash())) + case "eoa-load-test-consumer-deploy": + loadTestConsumerDeployCmd := flag.NewFlagSet("eoa-load-test-consumer-deploy", flag.ExitOnError) + consumerCoordinator := loadTestConsumerDeployCmd.String("coordinator-address", "", "coordinator address") + consumerLinkAddress := loadTestConsumerDeployCmd.String("link-address", "", "link-address") + helpers.ParseArgs(loadTestConsumerDeployCmd, os.Args[2:], "coordinator-address", "link-address") + consumerAddress, tx, _, err := vrf_load_test_external_sub_owner.DeployVRFLoadTestExternalSubOwner( + owner, + ec, + common.HexToAddress(*consumerCoordinator), + common.HexToAddress(*consumerLinkAddress)) + helpers.PanicErr(err) + fmt.Println("Consumer address", consumerAddress, "TX", helpers.ExplorerLink(chainID, tx.Hash())) case "eoa-create-sub": createSubCmd := flag.NewFlagSet("eoa-create-sub", flag.ExitOnError) coordinatorAddress := createSubCmd.String("coordinator-address", "", "coordinator address") @@ -298,7 +455,7 @@ func main() { helpers.PanicErr(err) txadd, err := coordinator.AddConsumer(owner, *subID, common.HexToAddress(*consumerAddress)) helpers.PanicErr(err) - fmt.Println("Adding consumer", "TX hash", txadd.Hash()) + fmt.Println("Adding consumer", "TX hash", helpers.ExplorerLink(chainID, txadd.Hash())) case "eoa-create-fund-authorize-sub": // Lets just treat the owner key as the EOA controlling the sub cfaSubCmd := flag.NewFlagSet("eoa-create-fund-authorize-sub", flag.ExitOnError) @@ -358,6 +515,23 @@ func main() { tx, err := consumer.RequestRandomWords(owner, *subID, uint32(*cbGasLimit), uint16(*requestConfirmations), uint32(*numWords), keyHashBytes) helpers.PanicErr(err) fmt.Println("TX", helpers.ExplorerLink(chainID, tx.Hash())) + case "eoa-load-test-request": + request := flag.NewFlagSet("eoa-load-test-request", flag.ExitOnError) + consumerAddress := request.String("consumer-address", "", "consumer address") + subID := request.Uint64("sub-id", 0, "subscription ID") + requestConfirmations := request.Uint("request-confirmations", 3, "minimum request confirmations") + keyHash := request.String("key-hash", "", "key hash") + requests := request.Uint("requests", 10, "number of randomness requests to make") + helpers.ParseArgs(request, os.Args[2:], "consumer-address", "sub-id", "key-hash") + keyHashBytes := common.HexToHash(*keyHash) + consumer, err := vrf_load_test_external_sub_owner.NewVRFLoadTestExternalSubOwner( + common.HexToAddress(*consumerAddress), + ec) + helpers.PanicErr(err) + tx, err := consumer.RequestRandomWords(owner, *subID, uint16(*requestConfirmations), + keyHashBytes, uint16(*requests)) + helpers.PanicErr(err) + fmt.Println("TX", helpers.ExplorerLink(chainID, tx.Hash())) case "eoa-transfer-sub": trans := flag.NewFlagSet("eoa-transfer-sub", flag.ExitOnError) coordinatorAddress := trans.String("coordinator-address", "", "coordinator address") @@ -451,3 +625,56 @@ func main() { panic("unrecognized subcommand: " + os.Args[1]) } } + +func parseIntSlice(arg string) (ret []*big.Int, err error) { + parts := strings.Split(arg, ",") + ret = []*big.Int{} + for _, part := range parts { + i, err := strconv.ParseInt(part, 10, 64) + if err != nil { + return nil, err + } + ret = append(ret, big.NewInt(i)) + } + return ret, nil +} + +// decreasingBlockRange creates a continugous block range starting with +// block `start` and ending at block `end`. +func decreasingBlockRange(start, end *big.Int) (ret []*big.Int, err error) { + if start.Cmp(end) == -1 { + return nil, fmt.Errorf("start (%s) must be greater than end (%s)", start.String(), end.String()) + } + ret = []*big.Int{} + for i := new(big.Int).Set(start); i.Cmp(end) >= 0; i.Sub(i, big.NewInt(1)) { + ret = append(ret, new(big.Int).Set(i)) + } + return +} + +func getRlpHeaders(ec *ethclient.Client, blockNumbers []*big.Int) (headers [][]byte, err error) { + headers = [][]byte{} + for _, blockNum := range blockNumbers { + // Get child block since it's the one that has the parent hash in it's header. + h, err := ec.HeaderByNumber( + context.Background(), + new(big.Int).Set(blockNum).Add(blockNum, big.NewInt(1)), + ) + if err != nil { + return nil, fmt.Errorf("failed to get header: %+v", err) + } + rlpHeader, err := rlp.EncodeToBytes(h) + if err != nil { + return nil, fmt.Errorf("failed to encode rlp: %+v", err) + } + // Uncomment in case storeVerifyHeader calls are reverting, there may be an issue with the RLP + // encoding. + // h2, err := ec.HeaderByNumber(context.Background(), blockNum) + // if err != nil { + // return nil, fmt.Errorf("failed to get header: %v", err) + // } + // fmt.Println("block number:", blockNum, "blockhash:", h2.Hash(), "encoded header of next block:", common.Bytes2Hex(rlpHeader)) + headers = append(headers, rlpHeader) + } + return +} diff --git a/core/services/blockhashstore/bhs.go b/core/services/blockhashstore/bhs.go index d2c8ecf54f7..7fa992b6f4f 100644 --- a/core/services/blockhashstore/bhs.go +++ b/core/services/blockhashstore/bhs.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" uuid "github.com/satori/go.uuid" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/blockhash_store" "github.com/smartcontractkit/chainlink/core/services/pg" ) @@ -28,7 +28,7 @@ type BulletproofBHS struct { config bpBHSConfig jobID uuid.UUID fromAddress common.Address - bptxm bulletprooftxmanager.TxManager + txm txmgr.TxManager abi *abi.ABI bhs blockhash_store.BlockhashStoreInterface } @@ -37,7 +37,7 @@ type BulletproofBHS struct { func NewBulletproofBHS( config bpBHSConfig, fromAddress common.Address, - bptxm bulletprooftxmanager.TxManager, + txm txmgr.TxManager, bhs blockhash_store.BlockhashStoreInterface, ) (*BulletproofBHS, error) { bhsABI, err := blockhash_store.BlockhashStoreMetaData.GetAbi() @@ -49,7 +49,7 @@ func NewBulletproofBHS( return &BulletproofBHS{ config: config, fromAddress: fromAddress, - bptxm: bptxm, + txm: txm, abi: bhsABI, bhs: bhs, }, nil @@ -62,7 +62,7 @@ func (c *BulletproofBHS) Store(ctx context.Context, blockNum uint64) error { return errors.Wrap(err, "packing args") } - _, err = c.bptxm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err = c.txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: c.fromAddress, ToAddress: c.bhs.Address(), EncodedPayload: payload, @@ -70,7 +70,7 @@ func (c *BulletproofBHS) Store(ctx context.Context, blockNum uint64) error { // Set a queue size of 256. At most we store the blockhash of every block, and only the // latest 256 can possibly be stored. - Strategy: bulletprooftxmanager.NewQueueingTxStrategy(c.jobID, 256), + Strategy: txmgr.NewQueueingTxStrategy(c.jobID, 256), }, pg.WithParentCtx(ctx)) if err != nil { return errors.Wrap(err, "creating transaction") diff --git a/core/services/blockhashstore/delegate.go b/core/services/blockhashstore/delegate.go index 9d321b3c1fb..12898619d42 100644 --- a/core/services/blockhashstore/delegate.go +++ b/core/services/blockhashstore/delegate.go @@ -17,7 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -var _ job.Service = &service{} +var _ job.ServiceCtx = &service{} // Delegate creates BlockhashStore feeder jobs. type Delegate struct { @@ -45,7 +45,7 @@ func (d *Delegate) JobType() job.Type { } // ServicesForSpec satisfies the job.Delegate interface. -func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { +func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if jb.BlockhashStoreSpec == nil { return nil, errors.Errorf( "blockhashstore.Delegate expects a BlockhashStoreSpec to be present, got %+v", jb) @@ -118,7 +118,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { return uint64(head.Number), nil }) - return []job.Service{&service{ + return []job.ServiceCtx{&service{ feeder: feeder, pollPeriod: jb.BlockhashStoreSpec.PollPeriod, runTimeout: jb.BlockhashStoreSpec.RunTimeout, @@ -147,19 +147,19 @@ type service struct { } // Start the BHS feeder service, satisfying the job.Service interface. -func (s *service) Start() error { +func (s *service) Start(context.Context) error { return s.StartOnce("BHS Feeder Service", func() error { s.logger.Infow("Starting BHS feeder") - ticker := time.NewTicker(s.pollPeriod) + ticker := time.NewTicker(utils.WithJitter(s.pollPeriod)) s.parentCtx, s.cancel = context.WithCancel(context.Background()) go func() { defer close(s.done) + defer ticker.Stop() for { select { case <-ticker.C: s.runFeeder() case <-s.stop: - ticker.Stop() return } } diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 9334fa4370d..92d6de70278 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -8,24 +8,22 @@ import ( "reflect" "sync" - "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" - "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "go.uber.org/multierr" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-solana/pkg/solana" pkgterra "github.com/smartcontractkit/chainlink-terra/pkg/terra" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/chains/terra" - terratypes "github.com/smartcontractkit/chainlink/core/chains/terra/types" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -37,9 +35,10 @@ import ( "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keeper" "github.com/smartcontractkit/chainlink/core/services/keystore" + "github.com/smartcontractkit/chainlink/core/services/ocr" + "github.com/smartcontractkit/chainlink/core/services/ocr2" + "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" "github.com/smartcontractkit/chainlink/core/services/periodicbackup" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" @@ -53,14 +52,13 @@ import ( "github.com/smartcontractkit/chainlink/core/services/webhook" "github.com/smartcontractkit/chainlink/core/sessions" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) //go:generate mockery --name Application --output ../../internal/mocks/ --case=underscore // Application implements the common functions used in the core node. type Application interface { - Start() error + Start(ctx context.Context) error Stop() error GetLogger() logger.Logger GetHealthChecker() services.Checker @@ -79,11 +77,10 @@ type Application interface { JobSpawner() job.Spawner JobORM() job.ORM EVMORM() evmtypes.ORM - TerraORM() terratypes.ORM PipelineORM() pipeline.ORM BridgeORM() bridges.ORM SessionORM() sessions.ORM - BPTXMORM() bulletprooftxmanager.ORM + TxmORM() txmgr.ORM AddJobV2(ctx context.Context, job *job.Job) error DeleteJob(ctx context.Context, jobID int32) error RunWebhookJobV2(ctx context.Context, jobUUID uuid.UUID, requestBody string, meta pipeline.JSONSerializable) (int64, error) @@ -95,8 +92,9 @@ type Application interface { // Feeds GetFeedsService() feeds.Service - // ReplayFromBlock of blocks - ReplayFromBlock(chainID *big.Int, number uint64) error + // ReplayFromBlock replays logs from on or after the given block number. If forceBroadcast is + // set to true, consumers will reprocess data even if it has already been processed. + ReplayFromBlock(chainID *big.Int, number uint64, forceBroadcast bool) error // ID is unique to this particular application instance ID() uuid.UUID @@ -114,7 +112,7 @@ type ChainlinkApplication struct { pipelineRunner pipeline.Runner bridgeORM bridges.ORM sessionORM sessions.ORM - bptxmORM bulletprooftxmanager.ORM + txmORM txmgr.ORM FeedsService feeds.Service webhookJobRunner webhook.JobRunner Config config.GeneralConfig @@ -123,10 +121,11 @@ type ChainlinkApplication struct { SessionReaper utils.SleeperTask shutdownOnce sync.Once explorerClient synchronization.ExplorerClient - subservices []services.Service + subservices []services.ServiceCtx HealthChecker services.Checker Nurse *services.Nurse logger logger.Logger + closeLogger func() error sqlxDB *sqlx.DB started bool @@ -140,6 +139,7 @@ type ApplicationOpts struct { KeyStore keystore.Master Chains Chains Logger logger.Logger + CloseLogger func() error ExternalInitiatorManager webhook.ExternalInitiatorManager Version string } @@ -147,7 +147,17 @@ type ApplicationOpts struct { // Chains holds a ChainSet for each type of chain. type Chains struct { EVM evm.ChainSet - Terra terra.ChainSet + Terra terra.ChainSet // nil if disabled +} + +func (c *Chains) services() (s []services.ServiceCtx) { + if c.EVM != nil { + s = append(s, c.EVM) + } + if c.Terra != nil { + s = append(s, c.Terra) + } + return } // NewApplication initializes a new store if one is not already @@ -156,7 +166,7 @@ type Chains struct { // be used by the node. // TODO: Inject more dependencies here to save booting up useless stuff in tests func NewApplication(opts ApplicationOpts) (Application, error) { - var subservices []services.Service + var subservices []services.ServiceCtx db := opts.SqlxDB cfg := opts.Config keyStore := opts.KeyStore @@ -197,7 +207,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { if cfg.ExplorerURL() == nil && cfg.TelemetryIngressURL() != nil { if cfg.TelemetryIngressUseBatchSend() { telemetryIngressBatchClient = synchronization.NewTelemetryIngressBatchClient(cfg.TelemetryIngressURL(), - cfg.TelemetryIngressServerPubKey(), keyStore.CSA(), cfg.TelemetryIngressLogging(), globalLogger, cfg.TelemetryIngressBufferSize(), cfg.TelemetryIngressMaxBatchSize(), cfg.TelemetryIngressSendInterval()) + cfg.TelemetryIngressServerPubKey(), keyStore.CSA(), cfg.TelemetryIngressLogging(), globalLogger, cfg.TelemetryIngressBufferSize(), cfg.TelemetryIngressMaxBatchSize(), cfg.TelemetryIngressSendInterval(), cfg.TelemetryIngressSendTimeout(), cfg.TelemetryIngressUniConn()) monitoringEndpointGen = telemetry.NewIngressAgentBatchWrapper(telemetryIngressBatchClient) } else { @@ -220,7 +230,8 @@ func NewApplication(opts ApplicationOpts) (Application, error) { globalLogger.Info("DatabaseBackup: periodic database backups are disabled. To enable automatic backups, set DATABASE_BACKUP_MODE=lite or DATABASE_BACKUP_MODE=full") } - subservices = append(subservices, eventBroadcaster, chains.EVM) + subservices = append(subservices, eventBroadcaster) + subservices = append(subservices, chains.services()...) promReporter := promreporter.NewPromReporter(db.DB, globalLogger) subservices = append(subservices, promReporter) @@ -230,7 +241,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { sessionORM = sessions.NewORM(db, cfg.SessionTimeout().Duration(), globalLogger) pipelineRunner = pipeline.NewRunner(pipelineORM, cfg, chains.EVM, keyStore.Eth(), keyStore.VRF(), globalLogger) jobORM = job.NewORM(db, chains.EVM, pipelineORM, keyStore, globalLogger, cfg) - bptxmORM = bulletprooftxmanager.NewORM(db, globalLogger, cfg) + txmORM = txmgr.NewORM(db, globalLogger, cfg) ) for _, chain := range chains.EVM.Chains() { @@ -301,7 +312,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { } if cfg.FeatureOffchainReporting() { - delegates[job.OffchainReporting] = offchainreporting.NewDelegate( + delegates[job.OffchainReporting] = ocr.NewDelegate( db, jobORM, keyStore, @@ -324,14 +335,16 @@ func NewApplication(opts ApplicationOpts) (Application, error) { } if cfg.SolanaEnabled() { solanaRelayer := solana.NewRelayer(globalLogger.Named("Solana.Relayer")) - relay.AddRelayer(relaytypes.Solana, solanaRelayer) + solanaRelayerCtx := solanaRelayer + relay.AddRelayer(relaytypes.Solana, solanaRelayerCtx) } if cfg.TerraEnabled() { terraRelayer := pkgterra.NewRelayer(globalLogger.Named("Terra.Relayer"), chains.Terra) - relay.AddRelayer(relaytypes.Terra, terraRelayer) + terraRelayerCtx := terraRelayer + relay.AddRelayer(relaytypes.Terra, terraRelayerCtx) } subservices = append(subservices, relay) - delegates[job.OffchainReporting2] = offchainreporting2.NewDelegate( + delegates[job.OffchainReporting2] = ocr2.NewDelegate( db, jobORM, pipelineRunner, @@ -387,7 +400,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { pipelineORM: pipelineORM, bridgeORM: bridgeORM, sessionORM: sessionORM, - bptxmORM: bptxmORM, + txmORM: txmORM, FeedsService: feedsService, Config: cfg, webhookJobRunner: webhookJobRunner, @@ -398,6 +411,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { HealthChecker: healthChecker, Nurse: nurse, logger: globalLogger, + closeLogger: opts.CloseLogger, sqlxDB: opts.SqlxDB, @@ -407,7 +421,8 @@ func NewApplication(opts ApplicationOpts) (Application, error) { } for _, service := range app.subservices { - if err := app.HealthChecker.Register(reflect.TypeOf(service).String(), service); err != nil { + checkable := service.(services.Checkable) + if err := app.HealthChecker.Register(reflect.TypeOf(service).String(), checkable); err != nil { return nil, err } } @@ -441,10 +456,9 @@ func (app *ChainlinkApplication) SetServiceLogLevel(ctx context.Context, service return logger.NewORM(app.GetSqlxDB(), app.GetLogger()).SetServiceLogLevel(ctx, serviceName, level.String()) } -// Start all necessary services. If successful, nil will be returned. Also -// listens for interrupt signals from the operating system so that the -// application can be properly closed before the application exits. -func (app *ChainlinkApplication) Start() error { +// Start all necessary services. If successful, nil will be returned. +// Start sequence is aborted if the context gets cancelled. +func (app *ChainlinkApplication) Start(ctx context.Context) error { app.startStopMu.Lock() defer app.startStopMu.Unlock() if app.started { @@ -458,8 +472,13 @@ func (app *ChainlinkApplication) Start() error { } for _, subservice := range app.subservices { + if ctx.Err() != nil { + return errors.Wrap(ctx.Err(), "aborting start") + } + app.logger.Debugw("Starting service...", "serviceType", reflect.TypeOf(subservice)) - if err := subservice.Start(); err != nil { + + if err := subservice.Start(ctx); err != nil { return err } } @@ -497,47 +516,36 @@ func (app *ChainlinkApplication) stop() (err error) { panic("application is already stopped") } app.shutdownOnce.Do(func() { - done := make(chan error) - go func() { - var merr error - defer func() { - if lerr := app.logger.Sync(); lerr != nil { - merr = multierr.Append(merr, lerr) - } - }() - app.logger.Info("Gracefully exiting...") - - // Stop services in the reverse order from which they were started - for i := len(app.subservices) - 1; i >= 0; i-- { - service := app.subservices[i] - app.logger.Debugw("Closing service...", "serviceType", reflect.TypeOf(service)) - merr = multierr.Append(merr, service.Close()) - } - - app.logger.Debug("Stopping SessionReaper...") - merr = multierr.Append(merr, app.SessionReaper.Stop()) - app.logger.Debug("Closing HealthChecker...") - merr = multierr.Append(merr, app.HealthChecker.Close()) - if app.FeedsService != nil { - app.logger.Debug("Closing Feeds Service...") - merr = multierr.Append(merr, app.FeedsService.Close()) + defer func() { + if lerr := app.closeLogger(); lerr != nil { + err = multierr.Append(err, lerr) } + }() + app.logger.Info("Gracefully exiting...") - if app.Nurse != nil { - merr = multierr.Append(merr, app.Nurse.Close()) - } + // Stop services in the reverse order from which they were started + for i := len(app.subservices) - 1; i >= 0; i-- { + service := app.subservices[i] + app.logger.Debugw("Closing service...", "serviceType", reflect.TypeOf(service)) + err = multierr.Append(err, service.Close()) + } - app.logger.Info("Exited all services") + app.logger.Debug("Stopping SessionReaper...") + err = multierr.Append(err, app.SessionReaper.Stop()) + app.logger.Debug("Closing HealthChecker...") + err = multierr.Append(err, app.HealthChecker.Close()) + if app.FeedsService != nil { + app.logger.Debug("Closing Feeds Service...") + err = multierr.Append(err, app.FeedsService.Close()) + } - app.started = false - done <- err - }() - select { - case merr := <-done: - err = merr - case <-time.After(15 * time.Second): - err = errors.New("application timed out shutting down") + if app.Nurse != nil { + err = multierr.Append(err, app.Nurse.Close()) } + + app.logger.Info("Exited all services") + + app.started = false }) return err } @@ -578,17 +586,12 @@ func (app *ChainlinkApplication) EVMORM() evmtypes.ORM { return app.Chains.EVM.ORM() } -// TerraORM returns the Terra ORM. -func (app *ChainlinkApplication) TerraORM() terratypes.ORM { - return app.Chains.Terra.ORM() -} - func (app *ChainlinkApplication) PipelineORM() pipeline.ORM { return app.pipelineORM } -func (app *ChainlinkApplication) BPTXMORM() bulletprooftxmanager.ORM { - return app.bptxmORM +func (app *ChainlinkApplication) TxmORM() txmgr.ORM { + return app.txmORM } func (app *ChainlinkApplication) GetExternalInitiatorManager() webhook.ExternalInitiatorManager { @@ -638,7 +641,7 @@ func (app *ChainlinkApplication) RunJobV2( var runID int64 // Some jobs are special in that they do not have a task graph. - isBootstrap := jb.Type == job.OffchainReporting && jb.OffchainreportingOracleSpec != nil && jb.OffchainreportingOracleSpec.IsBootstrapPeer + isBootstrap := jb.Type == job.OffchainReporting && jb.OCROracleSpec != nil && jb.OCROracleSpec.IsBootstrapPeer if jb.Type.RequiresPipelineSpec() || !isBootstrap { var vars map[string]interface{} var saveTasks bool @@ -698,12 +701,13 @@ func (app *ChainlinkApplication) GetFeedsService() feeds.Service { return app.FeedsService } -func (app *ChainlinkApplication) ReplayFromBlock(chainID *big.Int, number uint64) error { +// ReplayFromBlock implements the Application interface. +func (app *ChainlinkApplication) ReplayFromBlock(chainID *big.Int, number uint64, forceBroadcast bool) error { chain, err := app.Chains.EVM.Get(chainID) if err != nil { return err } - chain.LogBroadcaster().ReplayFromBlock(int64(number)) + chain.LogBroadcaster().ReplayFromBlock(int64(number), forceBroadcast) return nil } diff --git a/core/services/checkable.go b/core/services/checkable.go index 07b8689709f..dabdbc7431a 100644 --- a/core/services/checkable.go +++ b/core/services/checkable.go @@ -2,8 +2,8 @@ package services // Checkable should be implemented by any type requiring health checks. type Checkable interface { - // Checkables should return nil if ready, or an error message otherwise. + // Ready should return nil if ready, or an error message otherwise. Ready() error - // Checkables should return nil if healthy, or an error message otherwise. + // Healthy should return nil if healthy, or an error message otherwise. Healthy() error } diff --git a/core/services/cron/cron.go b/core/services/cron/cron.go index e1a56782cd2..c834c295e91 100644 --- a/core/services/cron/cron.go +++ b/core/services/cron/cron.go @@ -1,6 +1,7 @@ package cron import ( + "context" "fmt" "github.com/robfig/cron/v3" @@ -41,7 +42,7 @@ func NewCronFromJobSpec( } // Start implements the job.Service interface. -func (cr *Cron) Start() error { +func (cr *Cron) Start(context.Context) error { cr.logger.Debug("Starting") _, err := cr.cronRunner.AddFunc(cr.jobSpec.CronSpec.CronSchedule, cr.runPipeline) diff --git a/core/services/cron/cron_test.go b/core/services/cron/cron_test.go index 3abee4ef877..a7fe76fd0d1 100644 --- a/core/services/cron/cron_test.go +++ b/core/services/cron/cron_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -47,7 +48,7 @@ func TestCronV2Pipeline(t *testing.T) { assert.Len(t, serviceArray, 1) service := serviceArray[0] - err = service.Start() + err = service.Start(testutils.Context(t)) require.NoError(t, err) defer service.Close() } @@ -68,7 +69,7 @@ func TestCronV2Schedule(t *testing.T) { service, err := cron.NewCronFromJobSpec(spec, runner, logger.TestLogger(t)) require.NoError(t, err) - err = service.Start() + err = service.Start(testutils.Context(t)) require.NoError(t, err) defer service.Close() diff --git a/core/services/cron/delegate.go b/core/services/cron/delegate.go index 72e7c868eef..b4f1891cb02 100644 --- a/core/services/cron/delegate.go +++ b/core/services/cron/delegate.go @@ -30,7 +30,7 @@ func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} // ServicesForSpec returns the scheduler to be used for running cron jobs -func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.Service, err error) { +func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.ServiceCtx, err error) { // TODO: we need to fill these out manually, find a better fix spec.PipelineSpec.JobName = spec.Name.ValueOrZero() spec.PipelineSpec.JobID = spec.ID @@ -44,5 +44,5 @@ func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.Service, err er return nil, err } - return []job.Service{cron}, nil + return []job.ServiceCtx{cron}, nil } diff --git a/core/services/directrequest/delegate.go b/core/services/directrequest/delegate.go index 7e4bbffc088..2b9132fb4c8 100644 --- a/core/services/directrequest/delegate.go +++ b/core/services/directrequest/delegate.go @@ -62,7 +62,7 @@ func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} // ServicesForSpec returns the log listener service for a direct request job -func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { +func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if jb.DirectRequestSpec == nil { return nil, errors.Errorf("DirectRequest: directrequest.Delegate expects a *job.DirectRequestSpec to be present, got %v", jb) } @@ -100,15 +100,15 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { minContractPayment: concreteSpec.MinContractPayment, chStop: make(chan struct{}), } - var services []job.Service + var services []job.ServiceCtx services = append(services, logListener) return services, nil } var ( - _ log.Listener = &listener{} - _ job.Service = &listener{} + _ log.Listener = &listener{} + _ job.ServiceCtx = &listener{} ) type listener struct { @@ -131,7 +131,7 @@ type listener struct { } // Start complies with job.Service -func (l *listener) Start() error { +func (l *listener) Start(context.Context) error { return l.StartOnce("DirectRequestListener", func() error { unsubscribeLogs := l.logBroadcaster.Register(l, log.ListenerOpts{ Contract: l.oracle.Address(), @@ -324,7 +324,7 @@ func (l *listener) handleOracleRequest(request *operator_wrapper.OperatorOracleR if loaded { runCloserChannel, _ = runCloserChannelIf.(chan struct{}) } - ctx, cancel := utils.CombinedContext(runCloserChannel, context.Background()) + ctx, cancel := utils.ContextFromChan(runCloserChannel) defer cancel() vars := pipeline.NewVarsFrom(map[string]interface{}{ diff --git a/core/services/directrequest/delegate_test.go b/core/services/directrequest/delegate_test.go index b1ec70e32e2..cfc8eb7ded0 100644 --- a/core/services/directrequest/delegate_test.go +++ b/core/services/directrequest/delegate_test.go @@ -10,6 +10,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/chains/evm/log" log_mocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" @@ -25,10 +30,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" pipeline_mocks "github.com/smartcontractkit/chainlink/core/services/pipeline/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - null "gopkg.in/guregu/null.v4" ) func TestDelegate_ServicesForSpec(t *testing.T) { @@ -59,7 +60,7 @@ func TestDelegate_ServicesForSpec(t *testing.T) { type DirectRequestUniverse struct { spec *job.Job runner *pipeline_mocks.Runner - service job.Service + service job.ServiceCtx jobORM job.ORM listener log.Listener logBroadcaster *log_mocks.Broadcaster @@ -151,7 +152,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { fn(nil) }).Once() - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) require.NotNil(t, uni.listener, "listener was nil; expected broadcaster.Register to have been called") @@ -190,7 +191,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { log.On("DecodedLog").Return(&logOracleRequest).Maybe() uni.logBroadcaster.On("MarkConsumed", mock.Anything, mock.Anything).Return(nil).Maybe() - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) log.AssertExpectations(t) @@ -230,7 +231,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { Topics: []common.Hash{{}, {}}, }) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) uni.listener.HandleLog(log) @@ -259,7 +260,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { log.On("DecodedLog").Return(&logCancelOracleRequest) uni.logBroadcaster.On("MarkConsumed", mock.Anything, mock.Anything).Return(nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) uni.listener.HandleLog(log) @@ -304,7 +305,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { cancelLog.On("DecodedLog").Return(&logCancelOracleRequest) uni.logBroadcaster.On("MarkConsumed", mock.Anything, mock.Anything).Return(nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) timeout := 5 * time.Second @@ -366,7 +367,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { fn(nil) }).Once().Return(false, nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) // check if the job exists under the correct ID @@ -411,7 +412,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { markConsumedLogAwaiter.ItHappened() }).Return(nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) uni.listener.HandleLog(log) @@ -461,7 +462,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { fn(nil) }).Once().Return(false, nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) // check if the job exists under the correct ID @@ -510,7 +511,7 @@ func TestDelegate_ServicesListenerHandleLog(t *testing.T) { markConsumedLogAwaiter.ItHappened() }).Return(nil) - err := uni.service.Start() + err := uni.service.Start(testutils.Context(t)) require.NoError(t, err) uni.listener.HandleLog(log) diff --git a/core/services/feeds/connection_manager.go b/core/services/feeds/connection_manager.go index d01f0d268f2..5af390335f2 100644 --- a/core/services/feeds/connection_manager.go +++ b/core/services/feeds/connection_manager.go @@ -107,7 +107,7 @@ func (mgr *connectionsManager) Connect(opts ConnectOpts) { // We only want to log if there was an error that did not occur // from a context cancel. if conn.ctx.Err() == nil { - mgr.lggr.Infof("Error connecting to Feeds Manager server: %v", err) + mgr.lggr.Warnf("Error connecting to Feeds Manager server: %v", err) } else { mgr.lggr.Infof("Closing wsrpc websocket connection: %v", err) } diff --git a/core/services/feeds/models.go b/core/services/feeds/models.go index 4d79bb2c755..36a439baa56 100644 --- a/core/services/feeds/models.go +++ b/core/services/feeds/models.go @@ -12,8 +12,9 @@ import ( // We only support OCR and FM for the feeds manager const ( - JobTypeFluxMonitor = "fluxmonitor" - JobTypeOffchainReporting = "ocr" + JobTypeFluxMonitor = "fluxmonitor" + JobTypeOffchainReporting = "ocr" + JobTypeOffchainReporting2 = "ocr2" ) // FeedsManager contains feeds manager related fields diff --git a/core/services/feeds/orm_test.go b/core/services/feeds/orm_test.go index 0138fc1d21d..f21e4442718 100644 --- a/core/services/feeds/orm_test.go +++ b/core/services/feeds/orm_test.go @@ -15,7 +15,7 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/feeds" "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/testdata/testspecs" "github.com/smartcontractkit/chainlink/core/utils" @@ -742,7 +742,7 @@ func createJob(t *testing.T, db *sqlx.DB, externalJobID uuid.UUID) *job.Job { _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), diff --git a/core/services/feeds/proto/feeds_manager.pb.go b/core/services/feeds/proto/feeds_manager.pb.go index a76037e1d62..87764fc14eb 100644 --- a/core/services/feeds/proto/feeds_manager.pb.go +++ b/core/services/feeds/proto/feeds_manager.pb.go @@ -7,11 +7,10 @@ package proto import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( @@ -27,6 +26,7 @@ const ( JobType_JOB_TYPE_UNSPECIFIED JobType = 0 JobType_JOB_TYPE_FLUX_MONITOR JobType = 1 JobType_JOB_TYPE_OCR JobType = 2 + JobType_JOB_TYPE_OCR2 JobType = 3 ) // Enum value maps for JobType. @@ -35,11 +35,13 @@ var ( 0: "JOB_TYPE_UNSPECIFIED", 1: "JOB_TYPE_FLUX_MONITOR", 2: "JOB_TYPE_OCR", + 3: "JOB_TYPE_OCR2", } JobType_value = map[string]int32{ "JOB_TYPE_UNSPECIFIED": 0, "JOB_TYPE_FLUX_MONITOR": 1, "JOB_TYPE_OCR": 2, + "JOB_TYPE_OCR2": 3, } ) @@ -933,49 +935,51 @@ var file_pkg_noderpc_proto_feeds_manager_proto_rawDesc = []byte{ 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x24, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x2a, 0x50, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, + 0x69, 0x64, 0x2a, 0x63, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x4c, 0x55, 0x58, 0x5f, 0x4d, 0x4f, 0x4e, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, - 0x43, 0x52, 0x10, 0x02, 0x2a, 0x68, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, - 0x0e, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x56, 0x4d, 0x10, - 0x01, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x53, 0x4f, 0x4c, 0x41, 0x4e, 0x41, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x49, - 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x52, 0x52, 0x41, 0x10, 0x03, 0x32, 0xd8, - 0x02, 0x0a, 0x0c, 0x46, 0x65, 0x65, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, - 0x40, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x17, - 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, - 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, - 0x65, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, - 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, - 0x62, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x43, 0x52, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x4f, 0x43, 0x52, 0x32, 0x10, 0x03, 0x2a, 0x68, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, + 0x56, 0x4d, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x53, 0x4f, 0x4c, 0x41, 0x4e, 0x41, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x43, + 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x52, 0x52, 0x41, 0x10, + 0x03, 0x32, 0xd8, 0x02, 0x0a, 0x0c, 0x46, 0x65, 0x65, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, + 0x62, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, - 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, - 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x4c, 0x0a, 0x0b, 0x4e, 0x6f, 0x64, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, - 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, - 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x73, 0x2d, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x72, 0x70, 0x63, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, + 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, + 0x66, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, + 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, + 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x4c, 0x0a, 0x0b, + 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x50, + 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, + 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x73, 0x2d, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6e, 0x6f, 0x64, 0x65, + 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index b76bc166724..226386fb73a 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -17,7 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/core/services/fluxmonitorv2" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/sqlx" @@ -171,6 +171,8 @@ func (s *service) SyncNodeInfo(id int64) error { jobtypes = append(jobtypes, pb.JobType_JOB_TYPE_FLUX_MONITOR) case JobTypeOffchainReporting: jobtypes = append(jobtypes, pb.JobType_JOB_TYPE_OCR) + case JobTypeOffchainReporting2: + jobtypes = append(jobtypes, pb.JobType_JOB_TYPE_OCR2) default: // NOOP } @@ -483,7 +485,7 @@ func (s *service) ApproveSpec(ctx context.Context, id int64, force bool) error { var address ethkey.EIP55Address switch j.Type { case job.OffchainReporting: - address = j.OffchainreportingOracleSpec.ContractAddress + address = j.OCROracleSpec.ContractAddress case job.FluxMonitor: address = j.FluxMonitorSpec.ContractAddress default: @@ -715,7 +717,7 @@ func (s *service) generateJob(spec string) (*job.Job, error) { if !s.cfg.Dev() && !s.cfg.FeatureOffchainReporting() { return nil, ErrOCRDisabled } - js, err = offchainreporting.ValidatedOracleSpecToml(s.chainSet, spec) + js, err = ocr.ValidatedOracleSpecToml(s.chainSet, spec) case job.FluxMonitor: js, err = fluxmonitorv2.ValidatedFluxMonitorSpec(s.cfg, spec) default: diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 65baadbf6c5..0578bb2137f 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -433,7 +433,7 @@ func Test_Service_SyncNodeInfo(t *testing.T) { multiaddr = "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju" feedsMgr = &feeds.FeedsManager{ ID: 1, - JobTypes: pq.StringArray{feeds.JobTypeFluxMonitor}, + JobTypes: pq.StringArray{feeds.JobTypeFluxMonitor, feeds.JobTypeOffchainReporting2}, IsOCRBootstrapPeer: true, OCRBootstrapPeerMultiaddr: null.StringFrom(multiaddr), } @@ -459,7 +459,7 @@ func Test_Service_SyncNodeInfo(t *testing.T) { // Mock the send svc.fmsClient.On("UpdateNode", ctx, &proto.UpdateNodeRequest{ - JobTypes: []proto.JobType{proto.JobType_JOB_TYPE_FLUX_MONITOR}, + JobTypes: []proto.JobType{proto.JobType_JOB_TYPE_FLUX_MONITOR, proto.JobType_JOB_TYPE_OCR2}, Chains: []*proto.Chain{{ Id: svc.cc.Chains()[0].ID().String(), Type: proto.ChainType_CHAIN_TYPE_EVM, diff --git a/core/services/fluxmonitorv2/contract_submitter.go b/core/services/fluxmonitorv2/contract_submitter.go index 7282d9420df..cdf220acb0a 100644 --- a/core/services/fluxmonitorv2/contract_submitter.go +++ b/core/services/fluxmonitorv2/contract_submitter.go @@ -43,7 +43,7 @@ func NewFluxAggregatorContractSubmitter( } } -// Submit submits the answer by writing a EthTx for the bulletprooftxmanager to +// Submit submits the answer by writing a EthTx for the txmgr to // pick up func (c *FluxAggregatorContractSubmitter) Submit(roundID *big.Int, submission *big.Int, qopts ...pg.QOpt) error { fromAddress, err := c.keyStore.GetRoundRobinAddress() diff --git a/core/services/fluxmonitorv2/delegate.go b/core/services/fluxmonitorv2/delegate.go index 84aea31aa0f..02c8bd51972 100644 --- a/core/services/fluxmonitorv2/delegate.go +++ b/core/services/fluxmonitorv2/delegate.go @@ -2,13 +2,14 @@ package fluxmonitorv2 import ( "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/pipeline" - "github.com/smartcontractkit/sqlx" ) // Delegate represents a Flux Monitor delegate @@ -54,7 +55,7 @@ func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} // ServicesForSpec returns the flux monitor service for the job spec -func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err error) { +func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err error) { if jb.FluxMonitorSpec == nil { return nil, errors.Errorf("Delegate expects a *job.FluxMonitorSpec to be present, got %v", jb) } @@ -62,10 +63,10 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err erro if err != nil { return nil, err } - strategy := bulletprooftxmanager.NewQueueingTxStrategy(jb.ExternalJobID, chain.Config().FMDefaultTransactionQueueDepth()) - var checker bulletprooftxmanager.TransmitCheckerSpec + strategy := txmgr.NewQueueingTxStrategy(jb.ExternalJobID, chain.Config().FMDefaultTransactionQueueDepth()) + var checker txmgr.TransmitCheckerSpec if chain.Config().FMSimulateTransactions() { - checker.CheckerType = bulletprooftxmanager.TransmitCheckerTypeSimulate + checker.CheckerType = txmgr.TransmitCheckerTypeSimulate } fm, err := NewFromJobSpec( @@ -85,5 +86,5 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err erro return nil, err } - return []job.Service{fm}, nil + return []job.ServiceCtx{fm}, nil } diff --git a/core/services/fluxmonitorv2/flux_monitor.go b/core/services/fluxmonitorv2/flux_monitor.go index e9c85ae7386..18502942b94 100644 --- a/core/services/fluxmonitorv2/flux_monitor.go +++ b/core/services/fluxmonitorv2/flux_monitor.go @@ -13,6 +13,8 @@ import ( "github.com/pkg/errors" "github.com/shopspring/decimal" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/bridges" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" "github.com/smartcontractkit/chainlink/core/chains/evm/log" @@ -25,7 +27,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) // PollRequest defines a request to initiate a poll @@ -264,7 +265,7 @@ const ( // Start implements the job.Service interface. It begins the CSP consumer in a // single goroutine to poll the price adapters and listen to NewRound events. -func (fm *FluxMonitor) Start() error { +func (fm *FluxMonitor) Start(context.Context) error { return fm.StartOnce("FluxMonitor", func() error { fm.logger.Debug("Starting Flux Monitor for job") @@ -308,8 +309,7 @@ func (fm *FluxMonitor) JobID() int32 { return fm.spec.JobID } func (fm *FluxMonitor) HandleLog(broadcast log.Broadcast) { log := broadcast.DecodedLog() if log == nil || reflect.ValueOf(log).IsNil() { - fm.logger.Error("HandleLog: ignoring nil value") - return + fm.logger.Panic("HandleLog: failed to handle log of type nil") } switch log := log.(type) { @@ -758,7 +758,7 @@ func (fm *FluxMonitor) respondToNewRoundLog(log flux_aggregator_wrapper.FluxAggr if err2 := fm.runner.InsertFinishedRun(&run, false, pg.WithQueryer(tx)); err2 != nil { return err2 } - if err2 := fm.queueTransactionForBPTXM(tx, run.ID, answer, roundState.RoundId, &log); err2 != nil { + if err2 := fm.queueTransactionForTxm(tx, run.ID, answer, roundState.RoundId, &log); err2 != nil { return err2 } return fm.logBroadcaster.MarkConsumed(lb, pg.WithQueryer(tx)) @@ -981,7 +981,7 @@ func (fm *FluxMonitor) pollIfEligible(pollReq PollRequestType, deviationChecker if err2 := fm.runner.InsertFinishedRun(&run, true, pg.WithQueryer(tx)); err2 != nil { return err2 } - if err2 := fm.queueTransactionForBPTXM(tx, run.ID, answer, roundState.RoundId, nil); err2 != nil { + if err2 := fm.queueTransactionForTxm(tx, run.ID, answer, roundState.RoundId, nil); err2 != nil { return err2 } if broadcast != nil { @@ -1057,7 +1057,7 @@ func (fm *FluxMonitor) initialRoundState() flux_aggregator_wrapper.OracleRoundSt return latestRoundState } -func (fm *FluxMonitor) queueTransactionForBPTXM(tx pg.Queryer, runID int64, answer decimal.Decimal, roundID uint32, log *flux_aggregator_wrapper.FluxAggregatorNewRound) error { +func (fm *FluxMonitor) queueTransactionForTxm(tx pg.Queryer, runID int64, answer decimal.Decimal, roundID uint32, log *flux_aggregator_wrapper.FluxAggregatorNewRound) error { // Submit the Eth Tx err := fm.contractSubmitter.Submit( new(big.Int).SetInt64(int64(roundID)), diff --git a/core/services/fluxmonitorv2/flux_monitor_test.go b/core/services/fluxmonitorv2/flux_monitor_test.go index c4b9653ad3c..44dbe594b65 100644 --- a/core/services/fluxmonitorv2/flux_monitor_test.go +++ b/core/services/fluxmonitorv2/flux_monitor_test.go @@ -19,11 +19,13 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" "github.com/smartcontractkit/chainlink/core/chains/evm/config" "github.com/smartcontractkit/chainlink/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flux_aggregator_wrapper" @@ -40,15 +42,14 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" pipelinemocks "github.com/smartcontractkit/chainlink/core/services/pipeline/mocks" - "github.com/smartcontractkit/sqlx" ) const oracleCount uint8 = 17 type answerSet struct{ latestAnswer, polledAnswer int64 } -func newORM(t *testing.T, db *sqlx.DB, cfg pg.LogConfig, txm bulletprooftxmanager.TxManager) fluxmonitorv2.ORM { - return fluxmonitorv2.NewORM(db, logger.TestLogger(t), cfg, txm, bulletprooftxmanager.SendEveryStrategy{}, bulletprooftxmanager.TransmitCheckerSpec{}) +func newORM(t *testing.T, db *sqlx.DB, cfg pg.LogConfig, txm txmgr.TxManager) fluxmonitorv2.ORM { + return fluxmonitorv2.NewORM(db, logger.TestLogger(t), cfg, txm, txmgr.SendEveryStrategy{}, txmgr.TransmitCheckerSpec{}) } var ( @@ -171,9 +172,7 @@ type setupOptions struct { // functional options to configure the setup func setup(t *testing.T, db *sqlx.DB, optionFns ...func(*setupOptions)) (*fluxmonitorv2.FluxMonitor, *testMocks) { t.Helper() - if testing.Short() { - t.Skip("skipping") - } + testutils.SkipShort(t, "long test") tm := setupMocks(t) options := setupOptions{ @@ -722,7 +721,7 @@ func TestPollingDeviationChecker_BuffersLogs(t *testing.T) { Once(). Run(func(mock.Arguments) { readyToAssert.ItHappened() }) - fm.Start() + fm.Start(testutils.Context(t)) var logBroadcasts []*logmocks.Broadcast @@ -802,7 +801,7 @@ func TestFluxMonitor_TriggerIdleTimeThreshold(t *testing.T) { }) } - fm.Start() + fm.Start(testutils.Context(t)) require.Len(t, idleDurationOccured, 0, "no Job Runs created") if tc.expectedToSubmit { @@ -871,7 +870,7 @@ func TestFluxMonitor_HibernationTickerFiresMultipleTimes(t *testing.T) { pollOccured := make(chan struct{}, 4) - err := fm.Start() + err := fm.Start(testutils.Context(t)) require.NoError(t, err) t.Cleanup(func() { fm.Close() }) @@ -974,7 +973,7 @@ func TestFluxMonitor_HibernationIsEnteredAndRetryTickerStopped(t *testing.T) { pollOccured := make(chan struct{}, 4) - err := fm.Start() + err := fm.Start(testutils.Context(t)) require.NoError(t, err) t.Cleanup(func() { fm.Close() }) @@ -1086,7 +1085,7 @@ func TestFluxMonitor_IdleTimerResetsOnNewRound(t *testing.T) { idleDurationOccured := make(chan struct{}, 4) initialPollOccurred := make(chan struct{}, 1) - fm.Start() + fm.Start(testutils.Context(t)) t.Cleanup(func() { fm.Close() }) // Initial Poll @@ -1208,7 +1207,7 @@ func TestFluxMonitor_RoundTimeoutCausesPoll_timesOutAtZero(t *testing.T) { fm.SetOracleAddress() fm.ExportedRoundState() - fm.Start() + fm.Start(testutils.Context(t)) g.Eventually(ch).Should(gomega.BeClosed()) @@ -1269,7 +1268,7 @@ func TestFluxMonitor_UsesPreviousRoundStateOnStartup_RoundTimeout(t *testing.T) Run(func(mock.Arguments) { close(chRoundState) }). Maybe() - fm.Start() + fm.Start(testutils.Context(t)) if test.expectedToSubmit { g.Eventually(chRoundState).Should(gomega.BeClosed()) @@ -1351,7 +1350,7 @@ func TestFluxMonitor_UsesPreviousRoundStateOnStartup_IdleTimer(t *testing.T) { }). Maybe() - require.NoError(t, fm.Start()) + require.NoError(t, fm.Start(testutils.Context(t))) t.Cleanup(func() { fm.Close() }) assert.Eventually(t, func() bool { return len(initialPollOccurred) == 1 }, 3*time.Second, 10*time.Millisecond) @@ -1427,7 +1426,7 @@ func TestFluxMonitor_RoundTimeoutCausesPoll_timesOutNotZero(t *testing.T) { Run(func(mock.Arguments) { close(chRoundState2) }). Once() - fm.Start() + fm.Start(testutils.Context(t)) tm.logBroadcaster.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) tm.logBroadcaster.On("MarkConsumed", mock.Anything, mock.Anything).Return(nil) @@ -1447,37 +1446,6 @@ func TestFluxMonitor_RoundTimeoutCausesPoll_timesOutNotZero(t *testing.T) { tm.AssertExpectations(t) } -func TestFluxMonitor_HandlesNilLogs(t *testing.T) { - t.Parallel() - - db := pgtest.NewSqlxDB(t) - fm, tm := setup(t, db) - - logBroadcast := new(logmocks.Broadcast) - var logNewRound *flux_aggregator_wrapper.FluxAggregatorNewRound - var logAnswerUpdated *flux_aggregator_wrapper.FluxAggregatorAnswerUpdated - var randomType interface{} - - logBroadcast.On("String").Maybe().Return("") - logBroadcast.On("DecodedLog").Return(logNewRound).Once() - assert.NotPanics(t, func() { - fm.HandleLog(logBroadcast) - }) - - logBroadcast.On("DecodedLog").Return(logAnswerUpdated).Once() - assert.NotPanics(t, func() { - fm.HandleLog(logBroadcast) - }) - - logBroadcast.On("DecodedLog").Return(randomType).Once() - assert.NotPanics(t, func() { - fm.HandleLog(logBroadcast) - }) - - tm.AssertExpectations(t) - logBroadcast.AssertExpectations(t) -} - func TestFluxMonitor_ConsumeLogBroadcast(t *testing.T) { t.Parallel() @@ -2002,7 +1970,7 @@ func TestFluxMonitor_DrumbeatTicker(t *testing.T) { Return(flux_aggregator_wrapper.OracleRoundState{RoundId: 4, EligibleToSubmit: false, LatestSubmission: answerBigInt, StartedAt: now()}, nil). Maybe() - fm.Start() + fm.Start(testutils.Context(t)) defer fm.Close() waitTime := 15 * time.Second diff --git a/core/services/fluxmonitorv2/helpers_test.go b/core/services/fluxmonitorv2/helpers_test.go index 1093356de22..fcbc85c581d 100644 --- a/core/services/fluxmonitorv2/helpers_test.go +++ b/core/services/fluxmonitorv2/helpers_test.go @@ -1,11 +1,20 @@ package fluxmonitorv2 import ( + "fmt" + "github.com/smartcontractkit/chainlink/core/chains/evm/log" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/core/utils" ) +// Format implements fmt.Formatter to always print just the pointer address. +// This is a hack to work around a race in github.com/stretchr/testify which +// prints internal fields, including the state of nested, embeded mutexes. +func (fm *FluxMonitor) Format(f fmt.State, verb rune) { + fmt.Fprintf(f, "%[1]T<%[1]p>", fm) +} + func (fm *FluxMonitor) ExportedPollIfEligible(threshold, absoluteThreshold float64) { fm.pollIfEligible(PollRequestTypePoll, NewDeviationChecker(threshold, absoluteThreshold, fm.logger), nil) } diff --git a/core/services/fluxmonitorv2/integrations_test.go b/core/services/fluxmonitorv2/integrations_test.go index 3b100fc8819..8f08df3a811 100644 --- a/core/services/fluxmonitorv2/integrations_test.go +++ b/core/services/fluxmonitorv2/integrations_test.go @@ -13,9 +13,6 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" - "github.com/smartcontractkit/sqlx" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -24,13 +21,22 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/chains/evm/log" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flags_wrapper" faw "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/logger" @@ -41,10 +47,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/atomic" - "gopkg.in/guregu/null.v4" ) const description = "exactly thirty-three characters!!" @@ -101,9 +103,7 @@ func WithMinMaxSubmission(min, max *big.Int) func(cfg *fluxAggregatorUniverseCon // arguments match the arguments of the same name in the FluxAggregator // constructor. func setupFluxAggregatorUniverse(t *testing.T, configOptions ...func(cfg *fluxAggregatorUniverseConfig)) fluxAggregatorUniverse { - if testing.Short() { - t.Skip("skipping due to VRFCoordinatorV2Universe") - } + testutils.SkipShort(t, "VRFCoordinatorV2Universe") cfg := &fluxAggregatorUniverseConfig{ MinSubmission: big.NewInt(0), MaxSubmission: big.NewInt(100000000000), @@ -218,7 +218,7 @@ func startApplication( config, _ := heavyweight.FullTestDB(t, dbName(t.Name()), true, true) setConfig(config) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, fa.backend, fa.key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) return app } diff --git a/core/services/fluxmonitorv2/orm.go b/core/services/fluxmonitorv2/orm.go index 747a710306b..1afd695a6d2 100644 --- a/core/services/fluxmonitorv2/orm.go +++ b/core/services/fluxmonitorv2/orm.go @@ -6,14 +6,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/sqlx" ) type transmitter interface { - CreateEthTransaction(newTx bulletprooftxmanager.NewTx, qopts ...pg.QOpt) (etx bulletprooftxmanager.EthTx, err error) + CreateEthTransaction(newTx txmgr.NewTx, qopts ...pg.QOpt) (etx txmgr.EthTx, err error) } //go:generate mockery --name ORM --output ./mocks/ --case=underscore @@ -31,13 +31,13 @@ type ORM interface { type orm struct { q pg.Q txm transmitter - strategy bulletprooftxmanager.TxStrategy - checker bulletprooftxmanager.TransmitCheckerSpec + strategy txmgr.TxStrategy + checker txmgr.TransmitCheckerSpec logger logger.Logger } // NewORM initializes a new ORM -func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig, txm transmitter, strategy bulletprooftxmanager.TxStrategy, checker bulletprooftxmanager.TransmitCheckerSpec) ORM { +func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.LogConfig, txm transmitter, strategy txmgr.TxStrategy, checker txmgr.TransmitCheckerSpec) ORM { namedLogger := lggr.Named("FluxMonitorORM") q := pg.NewQ(db, namedLogger, cfg) return &orm{ @@ -110,7 +110,7 @@ func (o *orm) CountFluxMonitorRoundStats() (count int, err error) { return count, errors.Wrap(err, "CountFluxMonitorRoundStats failed") } -// CreateEthTransaction creates an ethereum transaction for the BPTXM to pick up +// CreateEthTransaction creates an ethereum transaction for the Txm to pick up func (o *orm) CreateEthTransaction( fromAddress common.Address, toAddress common.Address, @@ -118,7 +118,7 @@ func (o *orm) CreateEthTransaction( gasLimit uint64, qopts ...pg.QOpt, ) (err error) { - _, err = o.txm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err = o.txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, diff --git a/core/services/fluxmonitorv2/orm_test.go b/core/services/fluxmonitorv2/orm_test.go index 53eaa9c25e0..ed5348eaaa0 100644 --- a/core/services/fluxmonitorv2/orm_test.go +++ b/core/services/fluxmonitorv2/orm_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" @@ -167,11 +167,11 @@ func TestORM_CreateEthTransaction(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() - strategy := new(bptxmmocks.TxStrategy) + strategy := new(txmmocks.TxStrategy) var ( - txm = new(bptxmmocks.TxManager) - orm = fluxmonitorv2.NewORM(db, logger.TestLogger(t), cfg, txm, strategy, bulletprooftxmanager.TransmitCheckerSpec{}) + txm = new(txmmocks.TxManager) + orm = fluxmonitorv2.NewORM(db, logger.TestLogger(t), cfg, txm, strategy, txmgr.TransmitCheckerSpec{}) _, from = cltest.MustInsertRandomKey(t, ethKeyStore, 0) to = testutils.NewAddress() @@ -179,14 +179,14 @@ func TestORM_CreateEthTransaction(t *testing.T) { gasLimit = uint64(21000) ) - txm.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txm.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: payload, GasLimit: gasLimit, Meta: nil, Strategy: strategy, - }).Return(bulletprooftxmanager.EthTx{}, nil).Once() + }).Return(txmgr.EthTx{}, nil).Once() orm.CreateEthTransaction(from, to, payload, gasLimit) diff --git a/core/services/job/common.go b/core/services/job/common.go index f26abdc2622..180f3ba6941 100644 --- a/core/services/job/common.go +++ b/core/services/job/common.go @@ -1,19 +1,54 @@ package job import ( + "context" "net/url" "time" ) //go:generate mockery --name Service --output ./mocks/ --case=underscore +//go:generate mockery --name ServiceCtx --output ./mocks/ --case=underscore type Service interface { Start() error Close() error } +// ServiceCtx is the same as Service, but Start method receives a context. +type ServiceCtx interface { + Start(context.Context) error + Close() error +} + type Config interface { DatabaseURL() url.URL TriggerFallbackDBPollInterval() time.Duration LogSQL() bool } + +// ServiceAdapter is a helper introduced for transitioning from Service to ServiceCtx. +type ServiceAdapter interface { + ServiceCtx +} + +type adapter struct { + service Service +} + +// NewServiceAdapter creates an adapter instance for the given Service. +func NewServiceAdapter(service Service) ServiceCtx { + return &adapter{ + service, + } +} + +// Start forwards the call to the underlying service.Start(). +// Context is not used in this case. +func (a adapter) Start(context.Context) error { + return a.service.Start() +} + +// Close forwards the call to the underlying service.Close(). +func (a adapter) Close() error { + return a.service.Close() +} diff --git a/core/services/job/helpers_test.go b/core/services/job/helpers_test.go index 3532a2faa18..78f3916bc51 100644 --- a/core/services/job/helpers_test.go +++ b/core/services/job/helpers_test.go @@ -17,7 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/sqlx" ) @@ -139,10 +139,10 @@ func makeOCRJobSpec(t *testing.T, transmitterAddress common.Address, b1, b2 stri } err := toml.Unmarshal([]byte(jobSpecText), &dbSpec) require.NoError(t, err) - var ocrspec job.OffchainReportingOracleSpec + var ocrspec job.OCROracleSpec err = toml.Unmarshal([]byte(jobSpecText), &ocrspec) require.NoError(t, err) - dbSpec.OffchainreportingOracleSpec = &ocrspec + dbSpec.OCROracleSpec = &ocrspec return &dbSpec } @@ -152,21 +152,21 @@ func makeOCRJobSpec(t *testing.T, transmitterAddress common.Address, b1, b2 stri // // https://github.com/stretchr/testify/issues/984 func compareOCRJobSpecs(t *testing.T, expected, actual job.Job) { - require.NotNil(t, expected.OffchainreportingOracleSpec) - require.Equal(t, expected.OffchainreportingOracleSpec.ContractAddress, actual.OffchainreportingOracleSpec.ContractAddress) - require.Equal(t, expected.OffchainreportingOracleSpec.P2PBootstrapPeers, actual.OffchainreportingOracleSpec.P2PBootstrapPeers) - require.Equal(t, expected.OffchainreportingOracleSpec.IsBootstrapPeer, actual.OffchainreportingOracleSpec.IsBootstrapPeer) - require.Equal(t, expected.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID, actual.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID) - require.Equal(t, expected.OffchainreportingOracleSpec.TransmitterAddress, actual.OffchainreportingOracleSpec.TransmitterAddress) - require.Equal(t, expected.OffchainreportingOracleSpec.ObservationTimeout, actual.OffchainreportingOracleSpec.ObservationTimeout) - require.Equal(t, expected.OffchainreportingOracleSpec.BlockchainTimeout, actual.OffchainreportingOracleSpec.BlockchainTimeout) - require.Equal(t, expected.OffchainreportingOracleSpec.ContractConfigTrackerSubscribeInterval, actual.OffchainreportingOracleSpec.ContractConfigTrackerSubscribeInterval) - require.Equal(t, expected.OffchainreportingOracleSpec.ContractConfigTrackerPollInterval, actual.OffchainreportingOracleSpec.ContractConfigTrackerPollInterval) - require.Equal(t, expected.OffchainreportingOracleSpec.ContractConfigConfirmations, actual.OffchainreportingOracleSpec.ContractConfigConfirmations) + require.NotNil(t, expected.OCROracleSpec) + require.Equal(t, expected.OCROracleSpec.ContractAddress, actual.OCROracleSpec.ContractAddress) + require.Equal(t, expected.OCROracleSpec.P2PBootstrapPeers, actual.OCROracleSpec.P2PBootstrapPeers) + require.Equal(t, expected.OCROracleSpec.IsBootstrapPeer, actual.OCROracleSpec.IsBootstrapPeer) + require.Equal(t, expected.OCROracleSpec.EncryptedOCRKeyBundleID, actual.OCROracleSpec.EncryptedOCRKeyBundleID) + require.Equal(t, expected.OCROracleSpec.TransmitterAddress, actual.OCROracleSpec.TransmitterAddress) + require.Equal(t, expected.OCROracleSpec.ObservationTimeout, actual.OCROracleSpec.ObservationTimeout) + require.Equal(t, expected.OCROracleSpec.BlockchainTimeout, actual.OCROracleSpec.BlockchainTimeout) + require.Equal(t, expected.OCROracleSpec.ContractConfigTrackerSubscribeInterval, actual.OCROracleSpec.ContractConfigTrackerSubscribeInterval) + require.Equal(t, expected.OCROracleSpec.ContractConfigTrackerPollInterval, actual.OCROracleSpec.ContractConfigTrackerPollInterval) + require.Equal(t, expected.OCROracleSpec.ContractConfigConfirmations, actual.OCROracleSpec.ContractConfigConfirmations) } func makeMinimalHTTPOracleSpec(t *testing.T, db *sqlx.DB, cfg config.GeneralConfig, contractAddress, transmitterAddress, keyBundle, fetchUrl, timeout string) *job.Job { - var ocrSpec = job.OffchainReportingOracleSpec{ + var ocrSpec = job.OCROracleSpec{ P2PBootstrapPeers: pq.StringArray{}, ObservationTimeout: models.Interval(10 * time.Second), BlockchainTimeout: models.Interval(20 * time.Second), @@ -182,13 +182,13 @@ func makeMinimalHTTPOracleSpec(t *testing.T, db *sqlx.DB, cfg config.GeneralConf } s := fmt.Sprintf(minimalNonBootstrapTemplate, contractAddress, transmitterAddress, keyBundle, fetchUrl, timeout) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: db, Client: cltest.NewEthClientMockWithDefaultChain(t), GeneralConfig: cfg}) - _, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + _, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &os) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &ocrSpec) require.NoError(t, err) - os.OffchainreportingOracleSpec = &ocrSpec + os.OCROracleSpec = &ocrSpec return &os } @@ -223,10 +223,10 @@ func makeOCRJobSpecFromToml(t *testing.T, jobSpecToml string) *job.Job { } err := toml.Unmarshal([]byte(jobSpecToml), &jb) require.NoError(t, err) - var ocrspec job.OffchainReportingOracleSpec + var ocrspec job.OCROracleSpec err = toml.Unmarshal([]byte(jobSpecToml), &ocrspec) require.NoError(t, err) - jb.OffchainreportingOracleSpec = &ocrspec + jb.OCROracleSpec = &ocrspec return &jb } diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index 1252917d5c3..c6c8a6392f6 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -6,9 +6,10 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/lib/pq" "github.com/pelletier/go-toml" uuid "github.com/satori/go.uuid" - "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" @@ -22,7 +23,8 @@ import ( "github.com/smartcontractkit/chainlink/core/services/directrequest" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keeper" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" + "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/vrf" "github.com/smartcontractkit/chainlink/core/services/webhook" @@ -55,13 +57,13 @@ func TestORM(t *testing.T) { require.NoError(t, err) var returnedSpec job.Job - var OCROracleSpec job.OffchainReportingOracleSpec + var OCROracleSpec job.OCROracleSpec err = db.Get(&returnedSpec, "SELECT * FROM jobs WHERE jobs.id = $1", jb.ID) require.NoError(t, err) - err = db.Get(&OCROracleSpec, "SELECT * FROM offchainreporting_oracle_specs WHERE offchainreporting_oracle_specs.id = $1", jb.OffchainreportingOracleSpecID) + err = db.Get(&OCROracleSpec, "SELECT * FROM ocr_oracle_specs WHERE ocr_oracle_specs.id = $1", jb.OCROracleSpecID) require.NoError(t, err) - returnedSpec.OffchainreportingOracleSpec = &OCROracleSpec + returnedSpec.OCROracleSpec = &OCROracleSpec compareOCRJobSpecs(t, *jb, returnedSpec) }) @@ -120,12 +122,12 @@ func TestORM(t *testing.T) { assert.Equal(t, specErrors[1].Description, ocrSpecError2) assert.True(t, specErrors[1].CreatedAt.After(specErrors[0].UpdatedAt)) var j2 job.Job - var OCROracleSpec job.OffchainReportingOracleSpec + var OCROracleSpec job.OCROracleSpec var jobSpecErrors []job.SpecError err = db.Get(&j2, "SELECT * FROM jobs WHERE jobs.id = $1", jobSpec.ID) require.NoError(t, err) - err = db.Get(&OCROracleSpec, "SELECT * FROM offchainreporting_oracle_specs WHERE offchainreporting_oracle_specs.id = $1", j2.OffchainreportingOracleSpecID) + err = db.Get(&OCROracleSpec, "SELECT * FROM ocr_oracle_specs WHERE ocr_oracle_specs.id = $1", j2.OCROracleSpecID) require.NoError(t, err) err = db.Select(&jobSpecErrors, "SELECT * FROM job_spec_errors WHERE job_spec_errors.job_id = $1", j2.ID) require.NoError(t, err) @@ -205,6 +207,7 @@ func TestORM(t *testing.T) { require.NoError(t, err) err = orm.CreateJob(&jb) + require.NoError(t, err) savedJob, err := orm.FindJob(context.Background(), jb.ID) require.NoError(t, err) require.Equal(t, jb.ID, savedJob.ID) @@ -244,7 +247,7 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ TransmitterAddress: address.Hex(), DS1BridgeName: bridge.Name.String(), DS2BridgeName: bridge2.Name.String(), @@ -254,12 +257,12 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { err = jobORM.CreateJob(&jb) require.NoError(t, err) - cltest.AssertCount(t, db, "offchainreporting_oracle_specs", 1) + cltest.AssertCount(t, db, "ocr_oracle_specs", 1) cltest.AssertCount(t, db, "pipeline_specs", 1) err = jobORM.DeleteJob(jb.ID) require.NoError(t, err) - cltest.AssertCount(t, db, "offchainreporting_oracle_specs", 0) + cltest.AssertCount(t, db, "ocr_oracle_specs", 0) cltest.AssertCount(t, db, "pipeline_specs", 0) cltest.AssertCount(t, db, "jobs", 0) }) @@ -327,17 +330,21 @@ func TestORM_CreateJob_VRFV2(t *testing.T) { config := evmtest.NewChainScopedConfig(t, cltest.NewTestGeneralConfig(t)) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config) - keyStore.OCR().Add(cltest.DefaultOCRKey) + require.NoError(t, keyStore.OCR().Add(cltest.DefaultOCRKey)) pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config}) jobORM := job.NewTestORM(t, db, cc, pipelineORM, keyStore, config) - jb, err := vrf.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{RequestedConfsDelay: 10}).Toml()) + fromAddresses := []string{cltest.NewEIP55Address().String(), cltest.NewEIP55Address().String()} + jb, err := vrf.ValidatedVRFSpec(testspecs.GenerateVRFSpec( + testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + FromAddresses: fromAddresses}). + Toml()) require.NoError(t, err) - err = jobORM.CreateJob(&jb) - require.NoError(t, err) + require.NoError(t, jobORM.CreateJob(&jb)) cltest.AssertCount(t, db, "vrf_specs", 1) cltest.AssertCount(t, db, "jobs", 1) var requestedConfsDelay int64 @@ -346,21 +353,27 @@ func TestORM_CreateJob_VRFV2(t *testing.T) { var requestTimeout time.Duration require.NoError(t, db.Get(&requestTimeout, `SELECT request_timeout FROM vrf_specs LIMIT 1`)) require.Equal(t, 24*time.Hour, requestTimeout) - jobORM.DeleteJob(jb.ID) + var fa pq.ByteaArray + require.NoError(t, db.Get(&fa, `SELECT from_addresses FROM vrf_specs LIMIT 1`)) + var actual []string + for _, b := range fa { + actual = append(actual, common.BytesToAddress(b).String()) + } + require.ElementsMatch(t, fromAddresses, actual) + require.NoError(t, jobORM.DeleteJob(jb.ID)) cltest.AssertCount(t, db, "vrf_specs", 0) cltest.AssertCount(t, db, "jobs", 0) jb, err = vrf.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{RequestTimeout: 1 * time.Hour}).Toml()) require.NoError(t, err) - err = jobORM.CreateJob(&jb) - require.NoError(t, err) + require.NoError(t, jobORM.CreateJob(&jb)) cltest.AssertCount(t, db, "vrf_specs", 1) cltest.AssertCount(t, db, "jobs", 1) require.NoError(t, db.Get(&requestedConfsDelay, `SELECT requested_confs_delay FROM vrf_specs LIMIT 1`)) require.Equal(t, int64(0), requestedConfsDelay) require.NoError(t, db.Get(&requestTimeout, `SELECT request_timeout FROM vrf_specs LIMIT 1`)) require.Equal(t, 1*time.Hour, requestTimeout) - jobORM.DeleteJob(jb.ID) + require.NoError(t, jobORM.DeleteJob(jb.ID)) cltest.AssertCount(t, db, "vrf_specs", 0) cltest.AssertCount(t, db, "jobs", 0) } @@ -408,7 +421,7 @@ func Test_FindJobs(t *testing.T) { _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb1, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb1, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: uuid.NewV4().String(), TransmitterAddress: address.Hex(), @@ -475,7 +488,7 @@ func Test_FindJob(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - job, err := offchainreporting.ValidatedOracleSpecToml(cc, + job, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), @@ -499,8 +512,8 @@ func Test_FindJob(t *testing.T) { require.Greater(t, jb.PipelineSpecID, int32(0)) require.NotNil(t, jb.PipelineSpec) - require.NotNil(t, jb.OffchainreportingOracleSpecID) - require.NotNil(t, jb.OffchainreportingOracleSpec) + require.NotNil(t, jb.OCROracleSpecID) + require.NotNil(t, jb.OCROracleSpec) }) t.Run("by external job id", func(t *testing.T) { @@ -512,12 +525,12 @@ func Test_FindJob(t *testing.T) { require.Greater(t, jb.PipelineSpecID, int32(0)) require.NotNil(t, jb.PipelineSpec) - require.NotNil(t, jb.OffchainreportingOracleSpecID) - require.NotNil(t, jb.OffchainreportingOracleSpec) + require.NotNil(t, jb.OCROracleSpecID) + require.NotNil(t, jb.OCROracleSpec) }) t.Run("by address", func(t *testing.T) { - jbID, err := orm.FindJobIDByAddress(job.OffchainreportingOracleSpec.ContractAddress) + jbID, err := orm.FindJobIDByAddress(job.OCROracleSpec.ContractAddress) require.NoError(t, err) assert.Equal(t, job.ID, jbID) @@ -534,7 +547,7 @@ func Test_FindJobsByPipelineSpecIDs(t *testing.T) { config := cltest.NewTestGeneralConfig(t) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config) - keyStore.OCR().Add(cltest.DefaultOCRKey) + require.NoError(t, keyStore.OCR().Add(cltest.DefaultOCRKey)) pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config}) @@ -584,7 +597,7 @@ func Test_FindPipelineRuns(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), @@ -631,7 +644,7 @@ func Test_PipelineRunsByJobID(t *testing.T) { db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config) - keyStore.OCR().Add(cltest.DefaultOCRKey) + require.NoError(t, keyStore.OCR().Add(cltest.DefaultOCRKey)) require.NoError(t, keyStore.P2P().Add(cltest.DefaultP2PKey)) pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config) @@ -643,7 +656,7 @@ func Test_PipelineRunsByJobID(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), @@ -701,7 +714,7 @@ func Test_FindPipelineRunIDsByJobID(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), @@ -750,7 +763,7 @@ func Test_FindPipelineRunsByIDs(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), @@ -849,7 +862,7 @@ func Test_CountPipelineRunsByJobID(t *testing.T) { externalJobID := uuid.NewV4() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, + jb, err := ocr.ValidatedOracleSpecToml(cc, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), TransmitterAddress: address.Hex(), diff --git a/core/services/job/mocks/delegate.go b/core/services/job/mocks/delegate.go index 75840f2b812..e6a6765ed2e 100644 --- a/core/services/job/mocks/delegate.go +++ b/core/services/job/mocks/delegate.go @@ -37,15 +37,15 @@ func (_m *Delegate) JobType() job.Type { } // ServicesForSpec provides a mock function with given fields: spec -func (_m *Delegate) ServicesForSpec(spec job.Job) ([]job.Service, error) { +func (_m *Delegate) ServicesForSpec(spec job.Job) ([]job.ServiceCtx, error) { ret := _m.Called(spec) - var r0 []job.Service - if rf, ok := ret.Get(0).(func(job.Job) []job.Service); ok { + var r0 []job.ServiceCtx + if rf, ok := ret.Get(0).(func(job.Job) []job.ServiceCtx); ok { r0 = rf(spec) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]job.Service) + r0 = ret.Get(0).([]job.ServiceCtx) } } diff --git a/core/services/job/mocks/service_ctx.go b/core/services/job/mocks/service_ctx.go new file mode 100644 index 00000000000..40db8ccf3c7 --- /dev/null +++ b/core/services/job/mocks/service_ctx.go @@ -0,0 +1,42 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// ServiceCtx is an autogenerated mock type for the ServiceCtx type +type ServiceCtx struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *ServiceCtx) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Start provides a mock function with given fields: _a0 +func (_m *ServiceCtx) Start(_a0 context.Context) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/core/services/job/mocks/spawner.go b/core/services/job/mocks/spawner.go index 40a4db61341..8aa7b525f24 100644 --- a/core/services/job/mocks/spawner.go +++ b/core/services/job/mocks/spawner.go @@ -3,6 +3,8 @@ package mocks import ( + context "context" + job "github.com/smartcontractkit/chainlink/core/services/job" mock "github.com/stretchr/testify/mock" @@ -114,13 +116,13 @@ func (_m *Spawner) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *Spawner) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Spawner) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } @@ -128,13 +130,13 @@ func (_m *Spawner) Start() error { return r0 } -// StartService provides a mock function with given fields: spec -func (_m *Spawner) StartService(spec job.Job) error { - ret := _m.Called(spec) +// StartService provides a mock function with given fields: ctx, spec +func (_m *Spawner) StartService(ctx context.Context, spec job.Job) error { + ret := _m.Called(ctx, spec) var r0 error - if rf, ok := ret.Get(0).(func(job.Job) error); ok { - r0 = rf(spec) + if rf, ok := ret.Get(0).(func(context.Context, job.Job) error); ok { + r0 = rf(ctx, spec) } else { r0 = ret.Error(0) } diff --git a/core/services/job/models.go b/core/services/job/models.go index 65109fa72bb..b32f721043c 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -8,12 +8,9 @@ import ( "strings" "time" - relaytypes "github.com/smartcontractkit/chainlink/core/services/relay/types" - - "github.com/pkg/errors" - "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" + "github.com/pkg/errors" uuid "github.com/satori/go.uuid" "gopkg.in/guregu/null.v4" @@ -22,6 +19,7 @@ import ( clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/services/pipeline" + relaytypes "github.com/smartcontractkit/chainlink/core/services/relay/types" "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -100,37 +98,37 @@ var ( ) type Job struct { - ID int32 `toml:"-"` - ExternalJobID uuid.UUID `toml:"externalJobID"` - OffchainreportingOracleSpecID *int32 - OffchainreportingOracleSpec *OffchainReportingOracleSpec - Offchainreporting2OracleSpecID *int32 - Offchainreporting2OracleSpec *OffchainReporting2OracleSpec - CronSpecID *int32 - CronSpec *CronSpec - DirectRequestSpecID *int32 - DirectRequestSpec *DirectRequestSpec - FluxMonitorSpecID *int32 - FluxMonitorSpec *FluxMonitorSpec - KeeperSpecID *int32 - KeeperSpec *KeeperSpec - VRFSpecID *int32 - VRFSpec *VRFSpec - WebhookSpecID *int32 - WebhookSpec *WebhookSpec - BlockhashStoreSpecID *int32 - BlockhashStoreSpec *BlockhashStoreSpec - BootstrapSpec *BootstrapSpec - BootstrapSpecID *int32 - PipelineSpecID int32 - PipelineSpec *pipeline.Spec - JobSpecErrors []SpecError - Type Type - SchemaVersion uint32 - Name null.String - MaxTaskDuration models.Interval - Pipeline pipeline.Pipeline `toml:"observationSource"` - CreatedAt time.Time + ID int32 `toml:"-"` + ExternalJobID uuid.UUID `toml:"externalJobID"` + OCROracleSpecID *int32 + OCROracleSpec *OCROracleSpec + OCR2OracleSpecID *int32 + OCR2OracleSpec *OCR2OracleSpec + CronSpecID *int32 + CronSpec *CronSpec + DirectRequestSpecID *int32 + DirectRequestSpec *DirectRequestSpec + FluxMonitorSpecID *int32 + FluxMonitorSpec *FluxMonitorSpec + KeeperSpecID *int32 + KeeperSpec *KeeperSpec + VRFSpecID *int32 + VRFSpec *VRFSpec + WebhookSpecID *int32 + WebhookSpec *WebhookSpec + BlockhashStoreSpecID *int32 + BlockhashStoreSpec *BlockhashStoreSpec + BootstrapSpec *BootstrapSpec + BootstrapSpecID *int32 + PipelineSpecID int32 + PipelineSpec *pipeline.Spec + JobSpecErrors []SpecError + Type Type + SchemaVersion uint32 + Name null.String + MaxTaskDuration models.Interval + Pipeline pipeline.Pipeline `toml:"observationSource"` + CreatedAt time.Time } func ExternalJobIDEncodeStringToTopic(id uuid.UUID) common.Hash { @@ -202,7 +200,8 @@ func (pr *PipelineRun) SetID(value string) error { return nil } -type OffchainReportingOracleSpec struct { +// OCROracleSpec defines the job spec for OCR jobs. +type OCROracleSpec struct { ID int32 `toml:"-"` ContractAddress ethkey.EIP55Address `toml:"contractAddress"` P2PBootstrapPeers pq.StringArray `toml:"p2pBootstrapPeers" db:"p2p_bootstrap_peers"` @@ -232,11 +231,13 @@ type OffchainReportingOracleSpec struct { UpdatedAt time.Time `toml:"-"` } -func (s OffchainReportingOracleSpec) GetID() string { +// GetID is a getter function that returns the ID of the spec. +func (s OCROracleSpec) GetID() string { return fmt.Sprintf("%v", s.ID) } -func (s *OffchainReportingOracleSpec) SetID(value string) error { +// SetID is a setter function that sets the ID of the spec. +func (s *OCROracleSpec) SetID(value string) error { ID, err := strconv.ParseInt(value, 10, 32) if err != nil { return err @@ -245,18 +246,22 @@ func (s *OffchainReportingOracleSpec) SetID(value string) error { return nil } -type RelayConfig map[string]interface{} +// JSONConfig is a Go mapping for JSON based database properties. +type JSONConfig map[string]interface{} -func (r RelayConfig) Bytes() []byte { +// Bytes returns the raw bytes +func (r JSONConfig) Bytes() []byte { b, _ := json.Marshal(r) return b } -func (r RelayConfig) Value() (driver.Value, error) { +// Value returns this instance serialized for database storage. +func (r JSONConfig) Value() (driver.Value, error) { return json.Marshal(r) } -func (r *RelayConfig) Scan(value interface{}) error { +// Scan reads the database value and returns an instance. +func (r *JSONConfig) Scan(value interface{}) error { b, ok := value.([]byte) if !ok { return errors.Errorf("expected bytes got %T", b) @@ -264,12 +269,21 @@ func (r *RelayConfig) Scan(value interface{}) error { return json.Unmarshal(b, &r) } +// OCR2PluginType defines supported OCR2 plugin types. +type OCR2PluginType string + +const ( + // Median refers to the median.Median type + Median OCR2PluginType = "median" +) + +// OCR2OracleSpec defines the job spec for OCR2 jobs. // Relay config is chain specific config for a relay (chain adapter). -type OffchainReporting2OracleSpec struct { +type OCR2OracleSpec struct { ID int32 `toml:"-"` ContractID string `toml:"contractID"` Relay relaytypes.Network `toml:"relay"` - RelayConfig RelayConfig `toml:"relayConfig"` + RelayConfig JSONConfig `toml:"relayConfig"` P2PBootstrapPeers pq.StringArray `toml:"p2pBootstrapPeers"` OCRKeyBundleID null.String `toml:"ocrKeyBundleID"` MonitoringEndpoint null.String `toml:"monitoringEndpoint"` @@ -277,16 +291,19 @@ type OffchainReporting2OracleSpec struct { BlockchainTimeout models.Interval `toml:"blockchainTimeout"` ContractConfigTrackerPollInterval models.Interval `toml:"contractConfigTrackerPollInterval"` ContractConfigConfirmations uint16 `toml:"contractConfigConfirmations"` - JuelsPerFeeCoinPipeline string `toml:"juelsPerFeeCoinSource"` + PluginConfig JSONConfig `toml:"pluginConfig"` + PluginType OCR2PluginType `toml:"pluginType"` CreatedAt time.Time `toml:"-"` UpdatedAt time.Time `toml:"-"` } -func (s OffchainReporting2OracleSpec) GetID() string { +// GetID is a getter function that returns the ID of the spec. +func (s OCR2OracleSpec) GetID() string { return fmt.Sprintf("%v", s.ID) } -func (s *OffchainReporting2OracleSpec) SetID(value string) error { +// SetID is a setter function that sets the ID of the spec. +func (s *OCR2OracleSpec) SetID(value string) error { ID, err := strconv.ParseInt(value, 10, 32) if err != nil { return err @@ -408,16 +425,16 @@ type KeeperSpec struct { type VRFSpec struct { ID int32 - CoordinatorAddress ethkey.EIP55Address `toml:"coordinatorAddress"` - PublicKey secp256k1.PublicKey `toml:"publicKey"` - MinIncomingConfirmations uint32 `toml:"minIncomingConfirmations"` - ConfirmationsEnv bool `toml:"-"` - EVMChainID *utils.Big `toml:"evmChainID"` - FromAddress *ethkey.EIP55Address `toml:"fromAddress"` - PollPeriod time.Duration `toml:"pollPeriod"` // For v2 jobs + CoordinatorAddress ethkey.EIP55Address `toml:"coordinatorAddress"` + PublicKey secp256k1.PublicKey `toml:"publicKey"` + MinIncomingConfirmations uint32 `toml:"minIncomingConfirmations"` + ConfirmationsEnv bool `toml:"-"` + EVMChainID *utils.Big `toml:"evmChainID"` + FromAddresses []ethkey.EIP55Address `toml:"fromAddresses"` + PollPeriod time.Duration `toml:"pollPeriod"` // For v2 jobs PollPeriodEnv bool RequestedConfsDelay int64 `toml:"requestedConfsDelay"` // For v2 jobs. Optional, defaults to 0 if not provided. - RequestTimeout time.Duration `toml:"requestTimeout"` // For v2 jobs. Optional, defaults to 24hr if not provided. + RequestTimeout time.Duration `toml:"requestTimeout"` // Optional, defaults to 24hr if not provided. CreatedAt time.Time `toml:"-"` UpdatedAt time.Time `toml:"-"` } @@ -468,7 +485,7 @@ type BootstrapSpec struct { ID int32 `toml:"-"` ContractID string `toml:"contractID"` Relay relaytypes.Network `toml:"relay"` - RelayConfig RelayConfig + RelayConfig JSONConfig MonitoringEndpoint null.String `toml:"monitoringEndpoint"` BlockchainTimeout models.Interval `toml:"blockchainTimeout"` ContractConfigTrackerPollInterval models.Interval `toml:"contractConfigTrackerPollInterval"` @@ -478,8 +495,8 @@ type BootstrapSpec struct { } // AsOCR2Spec transforms the bootstrap spec into a generic OCR2 format to enable code sharing between specs. -func (s BootstrapSpec) AsOCR2Spec() OffchainReporting2OracleSpec { - return OffchainReporting2OracleSpec{ +func (s BootstrapSpec) AsOCR2Spec() OCR2OracleSpec { + return OCR2OracleSpec{ ID: s.ID, ContractID: s.ContractID, Relay: s.Relay, diff --git a/core/services/job/orm.go b/core/services/job/orm.go index ad7379643fc..489c60b4b69 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -3,14 +3,19 @@ package job import ( "context" "database/sql" + "encoding/json" "fmt" "reflect" "time" - relaytypes "github.com/smartcontractkit/chainlink/core/services/relay/types" - + "github.com/ethereum/go-ethereum/common" + "github.com/jackc/pgconn" + "github.com/lib/pq" + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" "go.uber.org/multierr" + "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/chains/evm" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/logger" @@ -18,14 +23,12 @@ import ( "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" + medianconfig "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins/median/config" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" + relaytypes "github.com/smartcontractkit/chainlink/core/services/relay/types" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/sqlx" - - "github.com/jackc/pgconn" - "github.com/pkg/errors" - uuid "github.com/satori/go.uuid" ) var ( @@ -69,6 +72,7 @@ type orm struct { keyStore keystore.Master pipelineORM pipeline.ORM lggr logger.Logger + bridgeORM bridges.ORM } var _ ORM = (*orm)(nil) @@ -87,6 +91,7 @@ func NewORM( chainSet: chainSet, keyStore: keyStore, pipelineORM: pipelineORM, + bridgeORM: bridges.NewORM(db, lggr, cfg), lggr: namedLogger, } } @@ -94,28 +99,42 @@ func (o *orm) Close() error { return nil } -// CreateJob creates the job, and it's associated spec record. -// Expects an unmarshalled job spec as the jb argument i.e. output from ValidatedXX. -// Scans all persisted records back into jb -func (o *orm) CreateJob(jb *Job, qopts ...pg.QOpt) error { - q := o.q.WithOpts(qopts...) - p := jb.Pipeline +func (o *orm) assertBridgesExist(p pipeline.Pipeline) error { + var bridgeNames = make(map[bridges.BridgeName]struct{}) + var uniqueBridges []bridges.BridgeName for _, task := range p.Tasks { if task.Type() == pipeline.TaskTypeBridge { // Bridge must exist name := task.(*pipeline.BridgeTask).Name - - sql := `SELECT EXISTS(SELECT 1 FROM bridge_types WHERE name = $1);` - var exists bool - err := q.Get(&exists, sql, name) + bridge, err := bridges.ParseBridgeName(name) if err != nil { - return errors.Wrap(err, "CreateJob failed to check bridge") + return err } - if !exists { - return errors.Wrap(pipeline.ErrNoSuchBridge, name) + if _, have := bridgeNames[bridge]; have { + continue } + bridgeNames[bridge] = struct{}{} + uniqueBridges = append(uniqueBridges, bridge) + } + } + if len(uniqueBridges) != 0 { + _, err := o.bridgeORM.FindBridges(uniqueBridges) + if err != nil { + return err } } + return nil +} + +// CreateJob creates the job, and it's associated spec record. +// Expects an unmarshalled job spec as the jb argument i.e. output from ValidatedXX. +// Scans all persisted records back into jb +func (o *orm) CreateJob(jb *Job, qopts ...pg.QOpt) error { + q := o.q.WithOpts(qopts...) + p := jb.Pipeline + if err := o.assertBridgesExist(p); err != nil { + return err + } var jobID int32 err := q.Transaction(func(tx pg.Queryer) error { @@ -147,71 +166,86 @@ func (o *orm) CreateJob(jb *Job, qopts ...pg.QOpt) error { jb.FluxMonitorSpecID = &specID case OffchainReporting: var specID int32 - if jb.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID != nil { - _, err := o.keyStore.OCR().Get(jb.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID.String()) + if jb.OCROracleSpec.EncryptedOCRKeyBundleID != nil { + _, err := o.keyStore.OCR().Get(jb.OCROracleSpec.EncryptedOCRKeyBundleID.String()) if err != nil { - return errors.Wrapf(ErrNoSuchKeyBundle, "%v", jb.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID) + return errors.Wrapf(ErrNoSuchKeyBundle, "%v", jb.OCROracleSpec.EncryptedOCRKeyBundleID) } } - if jb.OffchainreportingOracleSpec.TransmitterAddress != nil { - _, err := o.keyStore.Eth().Get(jb.OffchainreportingOracleSpec.TransmitterAddress.Hex()) + if jb.OCROracleSpec.TransmitterAddress != nil { + _, err := o.keyStore.Eth().Get(jb.OCROracleSpec.TransmitterAddress.Hex()) if err != nil { - return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.OffchainreportingOracleSpec.TransmitterAddress) + return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.OCROracleSpec.TransmitterAddress) } } - sql := `INSERT INTO offchainreporting_oracle_specs (contract_address, p2p_bootstrap_peers, is_bootstrap_peer, encrypted_ocr_key_bundle_id, transmitter_address, + sql := `INSERT INTO ocr_oracle_specs (contract_address, p2p_bootstrap_peers, is_bootstrap_peer, encrypted_ocr_key_bundle_id, transmitter_address, observation_timeout, blockchain_timeout, contract_config_tracker_subscribe_interval, contract_config_tracker_poll_interval, contract_config_confirmations, evm_chain_id, created_at, updated_at, database_timeout, observation_grace_period, contract_transmitter_transmit_timeout) VALUES (:contract_address, :p2p_bootstrap_peers, :is_bootstrap_peer, :encrypted_ocr_key_bundle_id, :transmitter_address, :observation_timeout, :blockchain_timeout, :contract_config_tracker_subscribe_interval, :contract_config_tracker_poll_interval, :contract_config_confirmations, :evm_chain_id, NOW(), NOW(), :database_timeout, :observation_grace_period, :contract_transmitter_transmit_timeout) RETURNING id;` - err := pg.PrepareQueryRowx(tx, sql, &specID, jb.OffchainreportingOracleSpec) + err := pg.PrepareQueryRowx(tx, sql, &specID, jb.OCROracleSpec) if err != nil { return errors.Wrap(err, "failed to create OffchainreportingOracleSpec") } - jb.OffchainreportingOracleSpecID = &specID + jb.OCROracleSpecID = &specID case OffchainReporting2: var specID int32 - if jb.Offchainreporting2OracleSpec.OCRKeyBundleID.Valid { - _, err := o.keyStore.OCR2().Get(jb.Offchainreporting2OracleSpec.OCRKeyBundleID.String) + if jb.OCR2OracleSpec.OCRKeyBundleID.Valid { + _, err := o.keyStore.OCR2().Get(jb.OCR2OracleSpec.OCRKeyBundleID.String) if err != nil { - return errors.Wrapf(ErrNoSuchKeyBundle, "%v", jb.Offchainreporting2OracleSpec.OCRKeyBundleID) + return errors.Wrapf(ErrNoSuchKeyBundle, "%v", jb.OCR2OracleSpec.OCRKeyBundleID) } } - if jb.Offchainreporting2OracleSpec.TransmitterID.Valid { - switch jb.Offchainreporting2OracleSpec.Relay { + if jb.OCR2OracleSpec.TransmitterID.Valid { + switch jb.OCR2OracleSpec.Relay { case relaytypes.EVM: - _, err := o.keyStore.Eth().Get(jb.Offchainreporting2OracleSpec.TransmitterID.String) + _, err := o.keyStore.Eth().Get(jb.OCR2OracleSpec.TransmitterID.String) if err != nil { - return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.Offchainreporting2OracleSpec.TransmitterID) + return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.OCR2OracleSpec.TransmitterID) } case relaytypes.Solana: - _, err := o.keyStore.Solana().Get(jb.Offchainreporting2OracleSpec.TransmitterID.String) + _, err := o.keyStore.Solana().Get(jb.OCR2OracleSpec.TransmitterID.String) if err != nil { - return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.Offchainreporting2OracleSpec.TransmitterID) + return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.OCR2OracleSpec.TransmitterID) } case relaytypes.Terra: - _, err := o.keyStore.Terra().Get(jb.Offchainreporting2OracleSpec.TransmitterID.String) + _, err := o.keyStore.Terra().Get(jb.OCR2OracleSpec.TransmitterID.String) if err != nil { - return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.Offchainreporting2OracleSpec.TransmitterID) + return errors.Wrapf(ErrNoSuchTransmitterKey, "%v", jb.OCR2OracleSpec.TransmitterID) } } } + switch jb.OCR2OracleSpec.PluginType { + case Median: + var cfg medianconfig.PluginConfig + err := json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &cfg) + if err != nil { + return errors.Wrap(err, "failed to parse plugin config") + } + feePipeline, err := pipeline.Parse(cfg.JuelsPerFeeCoinPipeline) + if err != nil { + return err + } + if err2 := o.assertBridgesExist(*feePipeline); err2 != nil { + return err2 + } + } - sql := `INSERT INTO offchainreporting2_oracle_specs (contract_id, relay, relay_config, p2p_bootstrap_peers, ocr_key_bundle_id, transmitter_id, - blockchain_timeout, contract_config_tracker_poll_interval, contract_config_confirmations, juels_per_fee_coin_pipeline, + sql := `INSERT INTO ocr2_oracle_specs (contract_id, relay, relay_config, plugin_type, plugin_config, p2p_bootstrap_peers, ocr_key_bundle_id, transmitter_id, + blockchain_timeout, contract_config_tracker_poll_interval, contract_config_confirmations, created_at, updated_at) - VALUES (:contract_id, :relay, :relay_config, :p2p_bootstrap_peers, :ocr_key_bundle_id, :transmitter_id, - :blockchain_timeout, :contract_config_tracker_poll_interval, :contract_config_confirmations, :juels_per_fee_coin_pipeline, + VALUES (:contract_id, :relay, :relay_config, :plugin_type, :plugin_config, :p2p_bootstrap_peers, :ocr_key_bundle_id, :transmitter_id, + :blockchain_timeout, :contract_config_tracker_poll_interval, :contract_config_confirmations, NOW(), NOW()) RETURNING id;` - err := pg.PrepareQueryRowx(tx, sql, &specID, jb.Offchainreporting2OracleSpec) + err := pg.PrepareQueryRowx(tx, sql, &specID, jb.OCR2OracleSpec) if err != nil { return errors.Wrap(err, "failed to create Offchainreporting2OracleSpec") } - jb.Offchainreporting2OracleSpecID = &specID + jb.OCR2OracleSpecID = &specID case Keeper: var specID int32 sql := `INSERT INTO keeper_specs (contract_address, from_address, evm_chain_id, created_at, updated_at) @@ -232,11 +266,13 @@ func (o *orm) CreateJob(jb *Job, qopts ...pg.QOpt) error { jb.CronSpecID = &specID case VRF: var specID int32 - sql := `INSERT INTO vrf_specs (coordinator_address, public_key, min_incoming_confirmations, evm_chain_id, from_address, poll_period, requested_confs_delay, request_timeout, created_at, updated_at) - VALUES (:coordinator_address, :public_key, :min_incoming_confirmations, :evm_chain_id, :from_address, :poll_period, :requested_confs_delay, :request_timeout, NOW(), NOW()) + sql := `INSERT INTO vrf_specs (coordinator_address, public_key, min_incoming_confirmations, evm_chain_id, from_addresses, poll_period, requested_confs_delay, request_timeout, created_at, updated_at) + VALUES (:coordinator_address, :public_key, :min_incoming_confirmations, :evm_chain_id, :from_addresses, :poll_period, :requested_confs_delay, :request_timeout, NOW(), NOW()) RETURNING id;` - err := pg.PrepareQueryRowx(tx, sql, &specID, jb.VRFSpec) - pqErr, ok := err.(*pgconn.PgError) + + err := pg.PrepareQueryRowx(tx, sql, &specID, toVRFSpecRow(jb.VRFSpec)) + var pqErr *pgconn.PgError + ok := errors.As(err, &pqErr) if err != nil && ok && pqErr.Code == "23503" { if pqErr.ConstraintName == "vrf_specs_public_key_fkey" { return errors.Wrapf(ErrNoSuchPublicKey, "%s", jb.VRFSpec.PublicKey.String()) @@ -320,9 +356,9 @@ func (o *orm) InsertWebhookSpec(webhookSpec *WebhookSpec, qopts ...pg.QOpt) erro func (o *orm) InsertJob(job *Job, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) - query := `INSERT INTO jobs (pipeline_spec_id, name, schema_version, type, max_task_duration, offchainreporting_oracle_spec_id, offchainreporting2_oracle_spec_id, direct_request_spec_id, flux_monitor_spec_id, + query := `INSERT INTO jobs (pipeline_spec_id, name, schema_version, type, max_task_duration, ocr_oracle_spec_id, ocr2_oracle_spec_id, direct_request_spec_id, flux_monitor_spec_id, keeper_spec_id, cron_spec_id, vrf_spec_id, webhook_spec_id, blockhash_store_spec_id, bootstrap_spec_id, external_job_id, created_at) - VALUES (:pipeline_spec_id, :name, :schema_version, :type, :max_task_duration, :offchainreporting_oracle_spec_id, :offchainreporting2_oracle_spec_id, :direct_request_spec_id, :flux_monitor_spec_id, + VALUES (:pipeline_spec_id, :name, :schema_version, :type, :max_task_duration, :ocr_oracle_spec_id, :ocr2_oracle_spec_id, :direct_request_spec_id, :flux_monitor_spec_id, :keeper_spec_id, :cron_spec_id, :vrf_spec_id, :webhook_spec_id, :blockhash_store_spec_id, :bootstrap_spec_id, :external_job_id, NOW()) RETURNING *;` return q.GetNamed(query, job, job) @@ -331,13 +367,17 @@ func (o *orm) InsertJob(job *Job, qopts ...pg.QOpt) error { // DeleteJob removes a job func (o *orm) DeleteJob(id int32, qopts ...pg.QOpt) error { o.lggr.Debugw("Deleting job", "jobID", id) + // Added a 1 minute timeout to this query since this can take a long time as data increases. + // This was added specifically due to an issue with a database that had a millions of pipeline_runs and pipeline_task_runs + // and this query was taking ~40secs. + qopts = append(qopts, pg.WithLongQueryTimeout()) q := o.q.WithOpts(qopts...) query := ` WITH deleted_jobs AS ( DELETE FROM jobs WHERE id = $1 RETURNING pipeline_spec_id, - offchainreporting_oracle_spec_id, - offchainreporting2_oracle_spec_id, + ocr_oracle_spec_id, + ocr2_oracle_spec_id, keeper_spec_id, cron_spec_id, flux_monitor_spec_id, @@ -348,10 +388,10 @@ func (o *orm) DeleteJob(id int32, qopts ...pg.QOpt) error { bootstrap_spec_id ), deleted_oracle_specs AS ( - DELETE FROM offchainreporting_oracle_specs WHERE id IN (SELECT offchainreporting_oracle_spec_id FROM deleted_jobs) + DELETE FROM ocr_oracle_specs WHERE id IN (SELECT ocr_oracle_spec_id FROM deleted_jobs) ), deleted_oracle2_specs AS ( - DELETE FROM offchainreporting2_oracle_specs WHERE id IN (SELECT offchainreporting2_oracle_spec_id FROM deleted_jobs) + DELETE FROM ocr2_oracle_specs WHERE id IN (SELECT ocr2_oracle_spec_id FROM deleted_jobs) ), deleted_keeper_specs AS ( DELETE FROM keeper_specs WHERE id IN (SELECT keeper_spec_id FROM deleted_jobs) @@ -403,7 +443,8 @@ func (o *orm) RecordError(jobID int32, description string, qopts ...pg.QOpt) err updated_at = excluded.updated_at` err := q.ExecQ(sql, jobID, description, time.Now()) // Noop if the job has been deleted. - pqErr, ok := err.(*pgconn.PgError) + var pqErr *pgconn.PgError + ok := errors.As(err, &pqErr) if err != nil && ok && pqErr.Code == "23503" { if pqErr.ConstraintName == "job_spec_errors_v2_job_id_fkey" { return nil @@ -472,16 +513,16 @@ func (o *orm) FindJobs(offset, limit int) (jobs []Job, count int, err error) { } func (o *orm) LoadEnvConfigVars(jb *Job) error { - if jb.OffchainreportingOracleSpec != nil { - ch, err := o.chainSet.Get(jb.OffchainreportingOracleSpec.EVMChainID.ToInt()) + if jb.OCROracleSpec != nil { + ch, err := o.chainSet.Get(jb.OCROracleSpec.EVMChainID.ToInt()) if err != nil { return err } - newSpec, err := LoadEnvConfigVarsOCR(ch.Config(), o.keyStore.P2P(), *jb.OffchainreportingOracleSpec) + newSpec, err := LoadEnvConfigVarsOCR(ch.Config(), o.keyStore.P2P(), *jb.OCROracleSpec) if err != nil { return err } - jb.OffchainreportingOracleSpec = newSpec + jb.OCROracleSpec = newSpec } else if jb.VRFSpec != nil { ch, err := o.chainSet.Get(jb.VRFSpec.EVMChainID.ToInt()) if err != nil { @@ -544,7 +585,8 @@ type OCRSpecConfig interface { OCRKeyBundleID() (string, error) } -func LoadEnvConfigVarsLocalOCR(cfg OCRSpecConfig, os OffchainReportingOracleSpec) *OffchainReportingOracleSpec { +// LoadEnvConfigVarsLocalOCR loads local OCR env vars into the OCROracleSpec. +func LoadEnvConfigVarsLocalOCR(cfg OCRSpecConfig, os OCROracleSpec) *OCROracleSpec { if os.ObservationTimeout == 0 { os.ObservationTimeoutEnv = true os.ObservationTimeout = models.Interval(cfg.OCRObservationTimeout()) @@ -580,10 +622,11 @@ func LoadEnvConfigVarsLocalOCR(cfg OCRSpecConfig, os OffchainReportingOracleSpec return &os } -func LoadEnvConfigVarsOCR(cfg OCRSpecConfig, p2pStore keystore.P2P, os OffchainReportingOracleSpec) (*OffchainReportingOracleSpec, error) { +// LoadEnvConfigVarsOCR loads OCR env vars into the OCROracleSpec. +func LoadEnvConfigVarsOCR(cfg OCRSpecConfig, p2pStore keystore.P2P, os OCROracleSpec) (*OCROracleSpec, error) { if os.TransmitterAddress == nil { ta, err := cfg.OCRTransmitterAddress() - if errors.Cause(err) != config.ErrUnset { + if !errors.Is(errors.Cause(err), config.ErrUnset) { if err != nil { return nil, err } @@ -632,7 +675,7 @@ func (o *orm) FindJobIDByAddress(address ethkey.EIP55Address, qopts ...pg.QOpt) stmt := ` SELECT jobs.id FROM jobs -LEFT JOIN offchainreporting_oracle_specs ocrspec on ocrspec.contract_address = $1 AND ocrspec.id = jobs.offchainreporting_oracle_spec_id +LEFT JOIN ocr_oracle_specs ocrspec on ocrspec.contract_address = $1 AND ocrspec.id = jobs.ocr_oracle_spec_id LEFT JOIN flux_monitor_specs fmspec on fmspec.contract_address = $1 AND fmspec.id = jobs.flux_monitor_spec_id WHERE ocrspec.id IS NOT NULL OR fmspec.id IS NOT NULL ` @@ -936,12 +979,12 @@ func LoadAllJobTypes(tx pg.Queryer, job *Job) error { loadJobType(tx, job, "PipelineSpec", "pipeline_specs", &job.PipelineSpecID), loadJobType(tx, job, "FluxMonitorSpec", "flux_monitor_specs", job.FluxMonitorSpecID), loadJobType(tx, job, "DirectRequestSpec", "direct_request_specs", job.DirectRequestSpecID), - loadJobType(tx, job, "OffchainreportingOracleSpec", "offchainreporting_oracle_specs", job.OffchainreportingOracleSpecID), - loadJobType(tx, job, "Offchainreporting2OracleSpec", "offchainreporting2_oracle_specs", job.Offchainreporting2OracleSpecID), + loadJobType(tx, job, "OCROracleSpec", "ocr_oracle_specs", job.OCROracleSpecID), + loadJobType(tx, job, "OCR2OracleSpec", "ocr2_oracle_specs", job.OCR2OracleSpecID), loadJobType(tx, job, "KeeperSpec", "keeper_specs", job.KeeperSpecID), loadJobType(tx, job, "CronSpec", "cron_specs", job.CronSpecID), loadJobType(tx, job, "WebhookSpec", "webhook_specs", job.WebhookSpecID), - loadJobType(tx, job, "VRFSpec", "vrf_specs", job.VRFSpecID), + loadVRFJob(tx, job, job.VRFSpecID), loadJobType(tx, job, "BlockhashStoreSpec", "blockhash_store_specs", job.BlockhashStoreSpecID), loadJobType(tx, job, "BootstrapSpec", "bootstrap_specs", job.BootstrapSpecID), ) @@ -969,6 +1012,45 @@ func loadJobType(tx pg.Queryer, job *Job, field, table string, id *int32) error return nil } +func loadVRFJob(tx pg.Queryer, job *Job, id *int32) error { + if id == nil { + return nil + } + + var row vrfSpecRow + err := tx.Get(&row, `SELECT * FROM vrf_specs WHERE id = $1`, *id) + if err != nil { + return errors.Wrapf(err, `failed to load job type VRFSpec with id %d`, *id) + } + + job.VRFSpec = row.toVRFSpec() + return nil +} + +// vrfSpecRow is a helper type for reading and writing VRF specs to the database. This is necessary +// because the bytea[] in the DB is not automatically convertible to or from the spec's +// FromAddresses field. pq.ByteaArray must be used instead. +type vrfSpecRow struct { + *VRFSpec + FromAddresses pq.ByteaArray +} + +func toVRFSpecRow(spec *VRFSpec) vrfSpecRow { + addresses := make(pq.ByteaArray, len(spec.FromAddresses)) + for i, a := range spec.FromAddresses { + addresses[i] = a.Bytes() + } + return vrfSpecRow{VRFSpec: spec, FromAddresses: addresses} +} + +func (r vrfSpecRow) toVRFSpec() *VRFSpec { + for _, a := range r.FromAddresses { + r.VRFSpec.FromAddresses = append(r.VRFSpec.FromAddresses, + ethkey.EIP55AddressFromAddress(common.BytesToAddress(a))) + } + return r.VRFSpec +} + func loadJobSpecErrors(tx pg.Queryer, jb *Job) error { return errors.Wrapf(tx.Select(&jb.JobSpecErrors, `SELECT * FROM job_spec_errors WHERE job_id = $1`, jb.ID), "failed to load job spec errors for job %d", jb.ID) } diff --git a/core/services/job/orm_test.go b/core/services/job/orm_test.go index 75317b5aaa7..41bad6a32e7 100644 --- a/core/services/job/orm_test.go +++ b/core/services/job/orm_test.go @@ -24,7 +24,7 @@ func NewTestORM(t *testing.T, db *sqlx.DB, chainSet evm.ChainSet, pipelineORM pi func TestLoadEnvConfigVarsLocalOCR(t *testing.T) { config := configtest.NewTestGeneralConfig(t) chainConfig := evmtest.NewChainScopedConfig(t, config) - jobSpec := &OffchainReportingOracleSpec{} + jobSpec := &OCROracleSpec{} jobSpec = LoadEnvConfigVarsLocalOCR(chainConfig, *jobSpec) diff --git a/core/services/job/runner_integration_test.go b/core/services/job/runner_integration_test.go index c17349cb30d..85c394296ca 100644 --- a/core/services/job/runner_integration_test.go +++ b/core/services/job/runner_integration_test.go @@ -14,7 +14,11 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/core/chains" + evmconfigmocks "github.com/smartcontractkit/chainlink/core/chains/evm/config/mocks" + evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + ocr2mocks "github.com/smartcontractkit/chainlink/core/services/ocr2/mocks" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" @@ -25,7 +29,8 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/telemetry" "github.com/smartcontractkit/chainlink/core/services/webhook" @@ -52,7 +57,7 @@ func TestRunner(t *testing.T) { keyStore := cltest.NewKeyStore(t, db, config) ethKeyStore := keyStore.Eth() - ethClient, _, _ := cltest.NewEthMocksWithDefaultChain(t) + ethClient, _ := cltest.NewEthMocksWithDefaultChain(t) ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(cltest.Head(10), nil) ethClient.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil, nil) @@ -61,7 +66,7 @@ func TestRunner(t *testing.T) { runner := pipeline.NewRunner(pipelineORM, config, cc, nil, nil, logger.TestLogger(t)) jobORM := job.NewTestORM(t, db, cc, pipelineORM, keyStore, config) - runner.Start() + runner.Start(testutils.Context(t)) defer runner.Close() _, transmitterAddress := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -163,18 +168,123 @@ func TestRunner(t *testing.T) { }) t.Run("referencing a non-existent bridge should error", func(t *testing.T) { - _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) - jb := makeOCRJobSpecFromToml(t, fmt.Sprintf(` + // Create a random bridge name + _, b := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) + + // Reference a different one + cfg := new(evmconfigmocks.ChainScopedConfig) + cfg.On("Dev").Return(true) + cfg.On("ChainType").Return(chains.ChainType("")) + c := new(evmmocks.Chain) + c.On("Config").Return(cfg) + cs := new(evmmocks.ChainSet) + cs.On("Get", mock.Anything).Return(c, nil) + + jb, err := ocr.ValidatedOracleSpecToml(cs, ` type = "offchainreporting" schemaVersion = 1 + evmChainID = 1 + contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" + isBootstrapPeer = false + blockchainTimeout = "1s" + observationTimeout = "10s" + databaseTimeout = "2s" + contractConfigTrackerPollInterval="1s" + contractConfigConfirmations=1 + observationGracePeriod = "2s" + contractTransmitterTransmitTimeout = "500ms" + contractConfigTrackerSubscribeInterval="1s" observationSource = """ - ds1 [type=bridge name="%s"]; + ds1 [type=bridge name=blah]; + ds1_parse [type=jsonparse path="one,two"]; + ds1_multiply [type=multiply times=1.23]; + ds1 -> ds1_parse -> ds1_multiply -> answer1; + answer1 [type=median index=0]; """ - `, bridge.Name.String())) - err := jobORM.CreateJob(jb) - require.Error(t, - pipeline.ErrNoSuchBridge, - errors.Cause(err)) + `) + require.NoError(t, err) + // Should error creating it + err = jobORM.CreateJob(&jb) + require.Error(t, err) + assert.Contains(t, err.Error(), "not all bridges exist") + + // Same for ocr2 + cfg2 := new(ocr2mocks.Config) + cfg2.On("OCR2ContractTransmitterTransmitTimeout").Return(time.Second) + cfg2.On("OCR2DatabaseTimeout").Return(time.Second) + cfg2.On("Dev").Return(true) + jb2, err := validate.ValidatedOracleSpecToml(cfg2, fmt.Sprintf(` +type = "offchainreporting2" +pluginType = "median" +schemaVersion = 1 +relay = "evm" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +blockchainTimeout = "1s" +contractConfigTrackerPollInterval = "2s" +contractConfigConfirmations = 1 +observationSource = """ +ds1 [type=bridge name="%s"]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +answer1 [type=median index=0]; +""" +[relayConfig] +chainID = 1337 +[pluginConfig] +juelsPerFeeCoinSource = """ +ds1 [type=bridge name=blah]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +answer1 [type=median index=0]; +""" +`, b.Name.String())) + require.NoError(t, err) + // Should error creating it because of the juels per fee coin non-existent bridge + err = jobORM.CreateJob(&jb2) + require.Error(t, err) + assert.Contains(t, err.Error(), "not all bridges exist") + + // Duplicate bridge names that exist is ok + cfg2.On("OCR2ContractTransmitterTransmitTimeout").Return(time.Second) + cfg2.On("OCR2DatabaseTimeout").Return(time.Second) + cfg2.On("Dev").Return(true) + jb3, err := validate.ValidatedOracleSpecToml(cfg2, fmt.Sprintf(` +type = "offchainreporting2" +pluginType = "median" +schemaVersion = 1 +relay = "evm" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +blockchainTimeout = "1s" +contractConfigTrackerPollInterval = "2s" +contractConfigConfirmations = 1 +observationSource = """ +ds1 [type=bridge name="%s"]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +answer1 [type=median index=0]; +""" +[relayConfig] +chainID = 1337 +[pluginConfig] +juelsPerFeeCoinSource = """ +ds1 [type=bridge name="%s"]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +ds2 [type=bridge name="%s"]; +ds2_parse [type=jsonparse path="one,two"]; +ds2_multiply [type=multiply times=1.23]; +ds2 -> ds2_parse -> ds2_multiply -> answer1; +answer1 [type=median index=0]; +""" +`, b.Name.String(), b.Name.String(), b.Name.String())) + require.NoError(t, err) + // Should not error with duplicate bridges + err = jobORM.CreateJob(&jb3) + require.NoError(t, err) }) config.Overrides.DefaultHTTPAllowUnrestrictedNetworkAccess = null.BoolFrom(false) @@ -324,7 +434,7 @@ ds1 -> ds1_parse; """ ` s = fmt.Sprintf(s, cltest.NewEIP55Address(), "http://blah.com", "") - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + jb, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &jb) require.NoError(t, err) @@ -333,7 +443,7 @@ ds1 -> ds1_parse; require.NoError(t, err) // Required to create job spawner delegate. config.Overrides.P2PListenPort = null.IntFrom(2000) - sd := offchainreporting.NewDelegate( + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -356,7 +466,7 @@ ds1 -> ds1_parse; isBootstrapPeer = true ` s = fmt.Sprintf(s, cltest.NewEIP55Address()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + jb, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &jb) require.NoError(t, err) @@ -367,9 +477,11 @@ ds1 -> ds1_parse; config.Overrides.P2PListenPort = null.IntFrom(2000) lggr := logger.TestLogger(t) + _, err = keyStore.P2P().Create() + assert.NoError(t, err) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config, db, lggr) - require.NoError(t, pw.Start()) - sd := offchainreporting.NewDelegate( + require.NoError(t, pw.Start(testutils.Context(t))) + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -403,7 +515,7 @@ ds1 -> ds1_parse; config.Overrides.P2PBootstrapPeers = []string{"/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", "/dns4/chain.link/tcp/1235/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju"} config.Overrides.OCRKeyBundleID = null.NewString(kb.ID(), true) config.Overrides.OCRTransmitterAddress = &tAddress - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + jb, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &jb) require.NoError(t, err) @@ -411,17 +523,17 @@ ds1 -> ds1_parse; err = jobORM.CreateJob(&jb) require.NoError(t, err) // Assert the override - assert.Equal(t, jb.OffchainreportingOracleSpec.ObservationTimeout, models.Interval(cltest.MustParseDuration(t, "15s"))) + assert.Equal(t, jb.OCROracleSpec.ObservationTimeout, models.Interval(cltest.MustParseDuration(t, "15s"))) // Assert that this is default - assert.Equal(t, models.Interval(20000000000), jb.OffchainreportingOracleSpec.BlockchainTimeout) + assert.Equal(t, models.Interval(20000000000), jb.OCROracleSpec.BlockchainTimeout) assert.Equal(t, models.Interval(cltest.MustParseDuration(t, "1s")), jb.MaxTaskDuration) // Required to create job spawner delegate. config.Overrides.P2PListenPort = null.IntFrom(2000) lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config, db, lggr) - require.NoError(t, pw.Start()) - sd := offchainreporting.NewDelegate( + require.NoError(t, pw.Start(testutils.Context(t))) + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -440,7 +552,7 @@ ds1 -> ds1_parse; require.NoError(t, err) s := fmt.Sprintf(minimalNonBootstrapTemplate, cltest.NewEIP55Address(), transmitterAddress.Hex(), kb.ID(), "http://blah.com", "") - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + jb, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &jb) require.NoError(t, err) @@ -454,8 +566,8 @@ ds1 -> ds1_parse; config.Overrides.P2PListenPort = null.IntFrom(2000) lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config, db, lggr) - require.NoError(t, pw.Start()) - sd := offchainreporting.NewDelegate( + require.NoError(t, pw.Start(testutils.Context(t))) + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -471,7 +583,7 @@ ds1 -> ds1_parse; t.Run("test min bootstrap", func(t *testing.T) { s := fmt.Sprintf(minimalBootstrapTemplate, cltest.NewEIP55Address()) - jb, err := offchainreporting.ValidatedOracleSpecToml(cc, s) + jb, err := ocr.ValidatedOracleSpecToml(cc, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &jb) require.NoError(t, err) @@ -482,8 +594,8 @@ ds1 -> ds1_parse; config.Overrides.P2PListenPort = null.IntFrom(2000) lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config, db, lggr) - require.NoError(t, pw.Start()) - sd := offchainreporting.NewDelegate( + require.NoError(t, pw.Start(testutils.Context(t))) + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -512,9 +624,9 @@ ds1 -> ds1_parse; config.Overrides.P2PListenPort = null.IntFrom(2000) lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config, db, lggr) - require.NoError(t, pw.Start()) + require.NoError(t, pw.Start(testutils.Context(t))) - sd := offchainreporting.NewDelegate( + sd := ocr.NewDelegate( db, jobORM, keyStore, @@ -529,8 +641,9 @@ ds1 -> ds1_parse; // Return an error getting the contract code. ethClient.On("CodeAt", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("no such code")) + ctx := testutils.Context(t) for _, s := range services { - err = s.Start() + err = s.Start(ctx) require.NoError(t, err) } var se []job.SpecError @@ -638,8 +751,7 @@ ds1 -> ds1_parse; func TestRunner_Success_Callback_AsyncJob(t *testing.T) { t.Parallel() - ethClient, _, assertMockCalls := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.FeatureExternalInitiators = null.BoolFrom(true) @@ -648,7 +760,7 @@ func TestRunner_Success_Callback_AsyncJob(t *testing.T) { app := cltest.NewApplicationWithConfig(t, cfg, ethClient, cltest.UseRealExternalInitiatorManager) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: app.GetSqlxDB(), Client: ethClient, GeneralConfig: cfg}) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) var ( eiName = "substrate-ei" @@ -816,8 +928,7 @@ func TestRunner_Success_Callback_AsyncJob(t *testing.T) { func TestRunner_Error_Callback_AsyncJob(t *testing.T) { t.Parallel() - ethClient, _, assertMockCalls := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.FeatureExternalInitiators = null.BoolFrom(true) @@ -826,7 +937,7 @@ func TestRunner_Error_Callback_AsyncJob(t *testing.T) { app := cltest.NewApplicationWithConfig(t, cfg, ethClient, cltest.UseRealExternalInitiatorManager) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: app.GetSqlxDB(), Client: ethClient, GeneralConfig: cfg}) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) var ( eiName = "substrate-ei" diff --git a/core/services/job/spawner.go b/core/services/job/spawner.go index 362288d21b9..77c12f5232a 100644 --- a/core/services/job/spawner.go +++ b/core/services/job/spawner.go @@ -8,12 +8,12 @@ import ( "sync" "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) //go:generate mockery --name Spawner --output ./mocks/ --case=underscore @@ -24,14 +24,14 @@ type ( // services that perform the work described by job specs. Each active job spec // has 1 or more of these services associated with it. Spawner interface { - services.Service + services.ServiceCtx CreateJob(jb *Job, qopts ...pg.QOpt) error DeleteJob(jobID int32, qopts ...pg.QOpt) error ActiveJobs() map[int32]Job // NOTE: Prefer to use CreateJob, this is only publicly exposed for use in tests // to start a job that was previously manually inserted into DB - StartService(spec Job) error + StartService(ctx context.Context, spec Job) error } spawner struct { @@ -55,7 +55,7 @@ type ( // job. In case a given job type relies upon well-defined startup/shutdown // ordering for services, they are started in the order they are given // and stopped in reverse order. - ServicesForSpec(spec Job) ([]Service, error) + ServicesForSpec(spec Job) ([]ServiceCtx, error) AfterJobCreated(spec Job) BeforeJobDeleted(spec Job) } @@ -63,7 +63,7 @@ type ( activeJob struct { delegate Delegate spec Job - services []Service + services []ServiceCtx } ) @@ -84,9 +84,10 @@ func NewSpawner(orm ORM, config Config, jobTypeDelegates map[Type]Delegate, db * return s } -func (js *spawner) Start() error { +// Start starts Spawner. +func (js *spawner) Start(ctx context.Context) error { return js.StartOnce("JobSpawner", func() error { - js.startAllServices() + js.startAllServices(ctx) return nil }) @@ -101,16 +102,16 @@ func (js *spawner) Close() error { }) } -func (js *spawner) startAllServices() { +func (js *spawner) startAllServices(ctx context.Context) { // TODO: rename to find AllJobs specs, _, err := js.orm.FindJobs(0, math.MaxUint32) if err != nil { - js.lggr.Errorf("Couldn't fetch unclaimed jobs: %v", err) + js.lggr.Criticalf("Couldn't fetch unclaimed jobs: %v", err) return } for _, spec := range specs { - if err = js.StartService(spec); err != nil { + if err = js.StartService(ctx, spec); err != nil { js.lggr.Errorf("Couldn't start service %v: %v", spec.Name, err) } } @@ -128,6 +129,8 @@ func (js *spawner) stopAllServices() { } } +// stopService removes the job from memory and stop the services. +// It will always delete the job from memory even if closing the services fail. func (js *spawner) stopService(jobID int32) { js.lggr.Debugw("Stopping services for job", "jobID", jobID) js.activeJobsMu.Lock() @@ -149,7 +152,8 @@ func (js *spawner) stopService(jobID int32) { delete(js.activeJobs, jobID) } -func (js *spawner) StartService(spec Job) error { +// StartService starts service for the given job spec. +func (js *spawner) StartService(ctx context.Context, spec Job) error { js.activeJobsMu.Lock() defer js.activeJobsMu.Unlock() @@ -167,9 +171,9 @@ func (js *spawner) StartService(spec Job) error { services, err := delegate.ServicesForSpec(spec) if err != nil { js.lggr.Errorw("Error creating services for job", "jobID", spec.ID, "error", err) - ctx, cancel := utils.ContextFromChan(js.chStop) + cctx, cancel := utils.ContextFromChan(js.chStop) defer cancel() - js.orm.TryRecordError(spec.ID, err.Error(), pg.WithParentCtx(ctx)) + js.orm.TryRecordError(spec.ID, err.Error(), pg.WithParentCtx(cctx)) js.activeJobs[spec.ID] = aj return nil } @@ -177,9 +181,9 @@ func (js *spawner) StartService(spec Job) error { js.lggr.Debugw("JobSpawner: Starting services for job", "jobID", spec.ID, "count", len(services)) for _, service := range services { - err := service.Start() + err = service.Start(ctx) if err != nil { - js.lggr.Criticalw("Error creating service for job", "jobID", spec.ID, "error", err) + js.lggr.Criticalw("Error starting service for job", "jobID", spec.ID, "error", err) continue } aj.services = append(aj.services, service) @@ -199,7 +203,7 @@ func (js *spawner) CreateJob(jb *Job, qopts ...pg.QOpt) error { q := js.q.WithOpts(qopts...) if q.ParentCtx != nil { - ctx, cancel := utils.CombinedContext(js.chStop, q.ParentCtx) + ctx, cancel := utils.WithCloseChan(q.ParentCtx, js.chStop) defer cancel() q.ParentCtx = ctx } else { @@ -216,7 +220,7 @@ func (js *spawner) CreateJob(jb *Job, qopts ...pg.QOpt) error { return err } - if err = js.StartService(*jb); err != nil { + if err = js.StartService(q.ParentCtx, *jb); err != nil { return err } @@ -246,9 +250,6 @@ func (js *spawner) DeleteJob(jobID int32, qopts ...pg.QOpt) error { return errors.Errorf("job not found (id: %v)", jobID) } - // Stop the service if we own the job. - js.stopService(jobID) - lggr.Debugw("Callback: BeforeJobDeleted") aj.delegate.BeforeJobDeleted(aj.spec) lggr.Debugw("Callback: BeforeJobDeleted done") @@ -263,7 +264,7 @@ func (js *spawner) DeleteJob(jobID int32, qopts ...pg.QOpt) error { if parentCtx == nil { ctx, cancel = utils.ContextFromChan(js.chStop) } else { - ctx, cancel = utils.CombinedContext(js.chStop, parentCtx) + ctx, cancel = utils.WithCloseChan(parentCtx, js.chStop) } return ctx } @@ -273,6 +274,10 @@ func (js *spawner) DeleteJob(jobID int32, qopts ...pg.QOpt) error { return err } + // Stop the service if we own the job. + // this will remove the job from memory, which will always happen even if closing the services fail. + js.stopService(jobID) + lggr.Infow("Stopped and deleted job") return nil @@ -310,7 +315,8 @@ func (n *NullDelegate) JobType() Type { return n.Type } -func (n *NullDelegate) ServicesForSpec(spec Job) (s []Service, err error) { +// ServicesForSpec does no-op. +func (n *NullDelegate) ServicesForSpec(spec Job) (s []ServiceCtx, err error) { return } diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 42c3c5b3cb9..b16ed866c19 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -9,22 +9,24 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/sqlx" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/job/mocks" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) type delegate struct { jobType job.Type - services []job.Service + services []job.ServiceCtx jobID int32 chContinueCreatingServices chan struct{} job.Delegate @@ -34,7 +36,8 @@ func (d delegate) JobType() job.Type { return d.jobType } -func (d delegate) ServicesForSpec(js job.Job) ([]job.Service, error) { +// ServicesForSpec satisfies the job.Delegate interface. +func (d delegate) ServicesForSpec(js job.Job) ([]job.ServiceCtx, error) { if js.Type != d.jobType { return nil, nil } @@ -57,13 +60,13 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config) - ethClient, _, _ := cltest.NewEthMocksWithDefaultChain(t) + ethClient, _ := cltest.NewEthMocksWithDefaultChain(t) ethClient.On("CallContext", mock.Anything, mock.Anything, "eth_getBlockByNumber", mock.Anything, false). Run(func(args mock.Arguments) { head := args.Get(1).(**evmtypes.Head) *head = cltest.Head(10) }). - Return(nil) + Return(nil).Maybe() cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{DB: db, Client: ethClient, GeneralConfig: config}) t.Run("should respect its dependents", func(t *testing.T) { @@ -82,7 +85,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { result <- false } }() - spawner.Start() + spawner.Start(testutils.Context(t)) assert.True(t, <-result, "failed to signal to dependents") }) @@ -93,25 +96,25 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { lggr := logger.TestLogger(t) orm := job.NewTestORM(t, db, cc, pipeline.NewORM(db, lggr, config), keyStore, config) eventuallyA := cltest.NewAwaiter() - serviceA1 := new(mocks.Service) - serviceA2 := new(mocks.Service) - serviceA1.On("Start").Return(nil).Once() - serviceA2.On("Start").Return(nil).Once().Run(func(mock.Arguments) { eventuallyA.ItHappened() }) - dA := offchainreporting.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) - delegateA := &delegate{jobA.Type, []job.Service{serviceA1, serviceA2}, 0, make(chan struct{}), dA} + serviceA1 := new(mocks.ServiceCtx) + serviceA2 := new(mocks.ServiceCtx) + serviceA1.On("Start", mock.Anything).Return(nil).Once() + serviceA2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventuallyA.ItHappened() }) + dA := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) + delegateA := &delegate{jobA.Type, []job.ServiceCtx{serviceA1, serviceA2}, 0, make(chan struct{}), dA} eventuallyB := cltest.NewAwaiter() - serviceB1 := new(mocks.Service) - serviceB2 := new(mocks.Service) - serviceB1.On("Start").Return(nil).Once() - serviceB2.On("Start").Return(nil).Once().Run(func(mock.Arguments) { eventuallyB.ItHappened() }) + serviceB1 := new(mocks.ServiceCtx) + serviceB2 := new(mocks.ServiceCtx) + serviceB1.On("Start", mock.Anything).Return(nil).Once() + serviceB2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventuallyB.ItHappened() }) - dB := offchainreporting.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) - delegateB := &delegate{jobB.Type, []job.Service{serviceB1, serviceB2}, 0, make(chan struct{}), dB} + dB := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) + delegateB := &delegate{jobB.Type, []job.ServiceCtx{serviceB1, serviceB2}, 0, make(chan struct{}), dB} spawner := job.NewSpawner(orm, config, map[job.Type]job.Delegate{ jobA.Type: delegateA, jobB.Type: delegateB, }, db, lggr, nil) - spawner.Start() + spawner.Start(testutils.Context(t)) err := spawner.CreateJob(jobA) require.NoError(t, err) jobSpecIDA := jobA.ID @@ -153,15 +156,15 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { jobA := makeOCRJobSpec(t, address, bridge.Name.String(), bridge2.Name.String()) eventually := cltest.NewAwaiter() - serviceA1 := new(mocks.Service) - serviceA2 := new(mocks.Service) - serviceA1.On("Start").Return(nil).Once() - serviceA2.On("Start").Return(nil).Once().Run(func(mock.Arguments) { eventually.ItHappened() }) + serviceA1 := new(mocks.ServiceCtx) + serviceA2 := new(mocks.ServiceCtx) + serviceA1.On("Start", mock.Anything).Return(nil).Once() + serviceA2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventually.ItHappened() }) lggr := logger.TestLogger(t) orm := job.NewTestORM(t, db, cc, pipeline.NewORM(db, lggr, config), keyStore, config) - d := offchainreporting.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) - delegateA := &delegate{jobA.Type, []job.Service{serviceA1, serviceA2}, 0, nil, d} + d := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) + delegateA := &delegate{jobA.Type, []job.ServiceCtx{serviceA1, serviceA2}, 0, nil, d} spawner := job.NewSpawner(orm, config, map[job.Type]job.Delegate{ jobA.Type: delegateA, }, db, lggr, nil) @@ -170,7 +173,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { require.NoError(t, err) delegateA.jobID = jobA.ID - spawner.Start() + spawner.Start(testutils.Context(t)) eventually.AwaitOrFail(t) mock.AssertExpectationsForObjects(t, serviceA1, serviceA2) @@ -189,15 +192,15 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { jobA := makeOCRJobSpec(t, address, bridge.Name.String(), bridge2.Name.String()) eventuallyStart := cltest.NewAwaiter() - serviceA1 := new(mocks.Service) - serviceA2 := new(mocks.Service) - serviceA1.On("Start").Return(nil).Once() - serviceA2.On("Start").Return(nil).Once().Run(func(mock.Arguments) { eventuallyStart.ItHappened() }) + serviceA1 := new(mocks.ServiceCtx) + serviceA2 := new(mocks.ServiceCtx) + serviceA1.On("Start", mock.Anything).Return(nil).Once() + serviceA2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventuallyStart.ItHappened() }) lggr := logger.TestLogger(t) orm := job.NewTestORM(t, db, cc, pipeline.NewORM(db, lggr, config), keyStore, config) - d := offchainreporting.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) - delegateA := &delegate{jobA.Type, []job.Service{serviceA1, serviceA2}, 0, nil, d} + d := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, cc, logger.TestLogger(t)) + delegateA := &delegate{jobA.Type, []job.ServiceCtx{serviceA1, serviceA2}, 0, nil, d} spawner := job.NewSpawner(orm, config, map[job.Type]job.Delegate{ jobA.Type: delegateA, }, db, lggr, nil) @@ -207,7 +210,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { jobSpecIDA := jobA.ID delegateA.jobID = jobSpecIDA - spawner.Start() + spawner.Start(testutils.Context(t)) defer spawner.Close() eventuallyStart.AwaitOrFail(t) diff --git a/core/services/job/validate_test.go b/core/services/job/validate_test.go index 33fe7170cf3..20341844d63 100644 --- a/core/services/job/validate_test.go +++ b/core/services/job/validate_test.go @@ -20,7 +20,7 @@ type="blah" schemaVersion=1 `, assertion: func(t *testing.T, err error) { - require.True(t, errors.Cause(err) == ErrInvalidJobType) + require.True(t, errors.Is(errors.Cause(err), ErrInvalidJobType)) }, }, { @@ -30,7 +30,7 @@ type="vrf" schemaVersion=2 `, assertion: func(t *testing.T, err error) { - require.True(t, errors.Cause(err) == ErrInvalidSchemaVersion) + require.True(t, errors.Is(errors.Cause(err), ErrInvalidSchemaVersion)) }, }, { @@ -39,7 +39,7 @@ schemaVersion=2 type="vrf" `, assertion: func(t *testing.T, err error) { - require.True(t, errors.Cause(err) == ErrInvalidSchemaVersion) + require.True(t, errors.Is(errors.Cause(err), ErrInvalidSchemaVersion)) }, }, { @@ -49,7 +49,7 @@ type="vrf" schemaVersion=1 `, assertion: func(t *testing.T, err error) { - require.True(t, errors.Cause(err) == ErrNoPipelineSpec) + require.True(t, errors.Is(errors.Cause(err), ErrNoPipelineSpec)) }, }, { @@ -60,7 +60,7 @@ schemaVersion=1 observationSource="" `, assertion: func(t *testing.T, err error) { - require.True(t, errors.Cause(err) == ErrNoPipelineSpec) + require.True(t, errors.Is(errors.Cause(err), ErrNoPipelineSpec)) }, }, { diff --git a/core/services/keeper/common.go b/core/services/keeper/common.go index f70f2682731..834b2569608 100644 --- a/core/services/keeper/common.go +++ b/core/services/keeper/common.go @@ -14,6 +14,7 @@ type Config interface { KeeperDefaultTransactionQueueDepth() uint32 KeeperGasPriceBufferPercent() uint32 KeeperGasTipCapBufferPercent() uint32 + KeeperBaseFeeBufferPercent() uint32 KeeperMaximumGracePeriod() int64 KeeperRegistryCheckGasOverhead() uint64 KeeperRegistryPerformGasOverhead() uint64 diff --git a/core/services/keeper/delegate.go b/core/services/keeper/delegate.go index 88d86ecd31f..797ac415ae5 100644 --- a/core/services/keeper/delegate.go +++ b/core/services/keeper/delegate.go @@ -5,7 +5,7 @@ import ( "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_wrapper" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" @@ -49,7 +49,8 @@ func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} -func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.Service, err error) { +// ServicesForSpec satisfies the job.Delegate interface. +func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.ServiceCtx, err error) { // TODO: we need to fill these out manually, find a better fix spec.PipelineSpec.JobName = spec.Name.ValueOrZero() spec.PipelineSpec.JobID = spec.ID @@ -70,7 +71,7 @@ func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.Service, err er if err != nil { return nil, errors.Wrap(err, "unable to create keeper registry contract wrapper") } - strategy := bulletprooftxmanager.NewQueueingTxStrategy(spec.ExternalJobID, chain.Config().KeeperDefaultTransactionQueueDepth()) + strategy := txmgr.NewQueueingTxStrategy(spec.ExternalJobID, chain.Config().KeeperDefaultTransactionQueueDepth()) orm := NewORM(d.db, d.logger, chain.Config(), strategy) @@ -106,7 +107,7 @@ func (d *Delegate) ServicesForSpec(spec job.Job) (services []job.Service, err er chain.Config(), ) - return []job.Service{ + return []job.ServiceCtx{ registrySynchronizer, upkeepExecuter, }, nil diff --git a/core/services/keeper/integration_test.go b/core/services/keeper/integration_test.go index 0e02fa33686..e2b940ad373 100644 --- a/core/services/keeper/integration_test.go +++ b/core/services/keeper/integration_test.go @@ -13,17 +13,19 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/libocr/gethwrappers/link_token_interface" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/basic_upkeep_contract" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_wrapper" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/mock_v3_aggregator_contract" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keeper" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" webpresenters "github.com/smartcontractkit/chainlink/core/web/presenters" - "github.com/smartcontractkit/libocr/gethwrappers/link_token_interface" ) var ( @@ -119,13 +121,14 @@ func TestKeeperEthIntegration(t *testing.T) { config.Overrides.KeeperCheckUpkeepGasPriceFeatureEnabled = null.BoolFrom(true) // helps prevent missed heads config.Overrides.GlobalEvmHeadTrackerMaxBufferSize = null.IntFrom(100) + app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, backend, nodeKey) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // create job regAddrEIP55 := ethkey.EIP55AddressFromAddress(regAddr) job := cltest.MustInsertKeeperJob(t, db, korm, nodeAddressEIP55, regAddrEIP55) - err = app.JobSpawner().StartService(job) + err = app.JobSpawner().StartService(testutils.Context(t), job) require.NoError(t, err) // keeper job is triggered and payload is received diff --git a/core/services/keeper/orm.go b/core/services/keeper/orm.go index d04f82a0622..0e0796a92e9 100644 --- a/core/services/keeper/orm.go +++ b/core/services/keeper/orm.go @@ -1,26 +1,29 @@ package keeper import ( + "math/rand" + "time" + "github.com/lib/pq" "github.com/pkg/errors" + "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/services/pg" - "github.com/smartcontractkit/sqlx" ) // ORM implements ORM layer using PostgreSQL type ORM struct { q pg.Q config Config - strategy bulletprooftxmanager.TxStrategy + strategy txmgr.TxStrategy logger logger.Logger } // NewORM is the constructor of postgresORM -func NewORM(db *sqlx.DB, lggr logger.Logger, config Config, strategy bulletprooftxmanager.TxStrategy) ORM { +func NewORM(db *sqlx.DB, lggr logger.Logger, config Config, strategy txmgr.TxStrategy) ORM { lggr = lggr.Named("KeeperORM") return ORM{ q: pg.NewQ(db, lggr, config), @@ -96,12 +99,8 @@ DELETE FROM upkeep_registrations WHERE registry_id IN ( return rowsAffected, nil } -func (korm ORM) EligibleUpkeepsForRegistry( - registryAddress ethkey.EIP55Address, - blockNumber, gracePeriod int64, -) (upkeeps []UpkeepRegistration, err error) { - err = korm.q.Transaction(func(tx pg.Queryer) error { - stmt := ` +func (korm ORM) EligibleUpkeepsForRegistry(registryAddress ethkey.EIP55Address, blockNumber, gracePeriod int64) (upkeeps []UpkeepRegistration, err error) { + stmt := ` SELECT upkeep_registrations.* FROM upkeep_registrations INNER JOIN keeper_registries ON keeper_registries.id = upkeep_registrations.registry_id WHERE @@ -118,14 +117,17 @@ WHERE ) % keeper_registries.num_keepers ORDER BY upkeep_registrations.id ASC, upkeep_registrations.upkeep_id ASC ` - if err = tx.Select(&upkeeps, stmt, registryAddress, gracePeriod, blockNumber); err != nil { - return errors.Wrap(err, "EligibleUpkeepsForRegistry failed to get upkeep_registrations") - } - if err = loadUpkeepsRegistry(tx, upkeeps); err != nil { - return errors.Wrap(err, "EligibleUpkeepsForRegistry failed to load Registry on upkeeps") - } - return nil - }, pg.OptReadOnlyTx()) + if err = korm.q.Select(&upkeeps, stmt, registryAddress, gracePeriod, blockNumber); err != nil { + return upkeeps, errors.Wrap(err, "EligibleUpkeepsForRegistry failed to get upkeep_registrations") + } + if err = loadUpkeepsRegistry(korm.q, upkeeps); err != nil { + return upkeeps, errors.Wrap(err, "EligibleUpkeepsForRegistry failed to load Registry on upkeeps") + } + + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(upkeeps), func(i, j int) { + upkeeps[i], upkeeps[j] = upkeeps[j], upkeeps[i] + }) return upkeeps, err } diff --git a/core/services/keeper/orm_test.go b/core/services/keeper/orm_test.go index 1eff9e668f5..43cc1680ba2 100644 --- a/core/services/keeper/orm_test.go +++ b/core/services/keeper/orm_test.go @@ -6,17 +6,17 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" + "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keeper" - "github.com/smartcontractkit/sqlx" ) var ( @@ -32,7 +32,7 @@ func setupKeeperDB(t *testing.T) ( gcfg := cltest.NewTestGeneralConfig(t) db := pgtest.NewSqlxDB(t) cfg := evmtest.NewChainScopedConfig(t, gcfg) - orm := keeper.NewORM(db, logger.TestLogger(t), cfg, bulletprooftxmanager.SendEveryStrategy{}) + orm := keeper.NewORM(db, logger.TestLogger(t), cfg, txmgr.SendEveryStrategy{}) return db, cfg, orm } @@ -135,6 +135,37 @@ func TestKeeperDB_BatchDeleteUpkeepsForJob(t *testing.T) { require.Equal(t, int64(1), remainingUpkeep.UpkeepID) } +func TestKeeperDB_EligibleUpkeeps_Shuffle(t *testing.T) { + t.Parallel() + db, config, orm := setupKeeperDB(t) + ethKeyStore := cltest.NewKeyStore(t, db, config).Eth() + + blockheight := int64(63) + gracePeriod := int64(10) + + registry, _ := cltest.MustInsertKeeperRegistry(t, db, orm, ethKeyStore) + + ordered := [100]int64{} + for i := 0; i < 100; i++ { + k := newUpkeep(registry, int64(i)) + ordered[i] = int64(i) + err := orm.UpsertUpkeep(&k) + require.NoError(t, err) + } + + cltest.AssertCount(t, db, "upkeep_registrations", 100) + + eligibleUpkeeps, err := orm.EligibleUpkeepsForRegistry(registry.ContractAddress, blockheight, gracePeriod) + assert.NoError(t, err) + + require.Len(t, eligibleUpkeeps, 100) + shuffled := [100]int64{} + for i := 0; i < 100; i++ { + shuffled[i] = eligibleUpkeeps[i].UpkeepID + } + assert.NotEqualValues(t, ordered, shuffled) +} + func TestKeeperDB_EligibleUpkeeps_BlockCountPerTurn(t *testing.T) { t.Parallel() db, config, orm := setupKeeperDB(t) @@ -169,10 +200,11 @@ func TestKeeperDB_EligibleUpkeeps_BlockCountPerTurn(t *testing.T) { eligibleUpkeeps, err := orm.EligibleUpkeepsForRegistry(registry.ContractAddress, blockheight, gracePeriod) assert.NoError(t, err) + // 3 out of 5 are eligible, check that ids are 0,1 or 2 but order is shuffled so can not use equals require.Len(t, eligibleUpkeeps, 3) - assert.Equal(t, int64(0), eligibleUpkeeps[0].UpkeepID) - assert.Equal(t, int64(1), eligibleUpkeeps[1].UpkeepID) - assert.Equal(t, int64(2), eligibleUpkeeps[2].UpkeepID) + assert.Less(t, eligibleUpkeeps[0].UpkeepID, int64(3)) + assert.Less(t, eligibleUpkeeps[1].UpkeepID, int64(3)) + assert.Less(t, eligibleUpkeeps[2].UpkeepID, int64(3)) // preloads registry data assert.Equal(t, registry.ID, eligibleUpkeeps[0].RegistryID) @@ -211,9 +243,10 @@ func TestKeeperDB_EligibleUpkeeps_GracePeriod(t *testing.T) { eligibleUpkeeps, err := orm.EligibleUpkeepsForRegistry(registry.ContractAddress, blockheight, gracePeriod) assert.NoError(t, err) + // 2 out of 3 are eligible, check that ids are 0 or 1 but order is shuffled so can not use equals assert.Len(t, eligibleUpkeeps, 2) - assert.Equal(t, int64(0), eligibleUpkeeps[0].UpkeepID) - assert.Equal(t, int64(1), eligibleUpkeeps[1].UpkeepID) + assert.Less(t, eligibleUpkeeps[0].UpkeepID, int64(2)) + assert.Less(t, eligibleUpkeeps[1].UpkeepID, int64(2)) } func TestKeeperDB_EligibleUpkeeps_KeepersRotate(t *testing.T) { diff --git a/core/services/keeper/registry_synchronizer_core.go b/core/services/keeper/registry_synchronizer_core.go index 11d2c839ca1..6ff2f9053b4 100644 --- a/core/services/keeper/registry_synchronizer_core.go +++ b/core/services/keeper/registry_synchronizer_core.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "sync" "time" @@ -15,8 +16,8 @@ import ( // RegistrySynchronizer conforms to the Service and Listener interfaces var ( - _ job.Service = (*RegistrySynchronizer)(nil) - _ log.Listener = (*RegistrySynchronizer)(nil) + _ job.ServiceCtx = (*RegistrySynchronizer)(nil) + _ log.Listener = (*RegistrySynchronizer)(nil) ) // MailRoom holds the log mailboxes for all the log types that keeper cares about @@ -49,7 +50,7 @@ type RegistrySynchronizer struct { mailRoom MailRoom minIncomingConfirmations uint32 orm ORM - logger logger.Logger + logger logger.SugaredLogger wgDone sync.WaitGroup syncUpkeepQueueSize uint32 //Represents the max number of upkeeps that can be synced in parallel utils.StartStopOnce @@ -73,12 +74,13 @@ func NewRegistrySynchronizer(opts RegistrySynchronizerOptions) *RegistrySynchron mailRoom: mailRoom, minIncomingConfirmations: opts.MinIncomingConfirmations, orm: opts.ORM, - logger: opts.Logger.Named("RegistrySynchronizer"), + logger: logger.Sugared(opts.Logger.Named("RegistrySynchronizer")), syncUpkeepQueueSize: opts.SyncUpkeepQueueSize, } } -func (rs *RegistrySynchronizer) Start() error { +// Start starts RegistrySynchronizer. +func (rs *RegistrySynchronizer) Start(context.Context) error { return rs.StartOnce("RegistrySynchronizer", func() error { rs.wgDone.Add(2) go rs.run() diff --git a/core/services/keeper/registry_synchronizer_log_listener.go b/core/services/keeper/registry_synchronizer_log_listener.go index 6723cc0cc04..d66192ce441 100644 --- a/core/services/keeper/registry_synchronizer_log_listener.go +++ b/core/services/keeper/registry_synchronizer_log_listener.go @@ -14,7 +14,7 @@ func (rs *RegistrySynchronizer) JobID() int32 { func (rs *RegistrySynchronizer) HandleLog(broadcast log.Broadcast) { eventLog := broadcast.DecodedLog() if eventLog == nil || reflect.ValueOf(eventLog).IsNil() { - rs.logger.Errorf("HandleLog: ignoring nil value, type: %T", broadcast) + rs.logger.Panicf("HandleLog: ignoring nil value, type: %T", broadcast) return } diff --git a/core/services/keeper/registry_synchronizer_process_logs.go b/core/services/keeper/registry_synchronizer_process_logs.go index f7372619942..324facbaa41 100644 --- a/core/services/keeper/registry_synchronizer_process_logs.go +++ b/core/services/keeper/registry_synchronizer_process_logs.go @@ -26,7 +26,7 @@ func (rs *RegistrySynchronizer) handleSyncRegistryLog(done func()) { } broadcast, ok := i.(log.Broadcast) if !ok { - rs.logger.Errorf("invariant violation, expected log.Broadcast but got %T", broadcast) + rs.logger.AssumptionViolationf("expected log.Broadcast but got %T", broadcast) return } txHash := broadcast.RawLog().TxHash.Hex() @@ -58,7 +58,7 @@ func (rs *RegistrySynchronizer) handleUpkeepCanceledLogs(done func()) { } broadcast, ok := i.(log.Broadcast) if !ok { - rs.logger.Errorf("invariant violation, expected log.Broadcast but got %T", broadcast) + rs.logger.AssumptionViolationf("expected log.Broadcast but got %T", broadcast) continue } rs.handleUpkeepCancelled(broadcast) @@ -78,7 +78,7 @@ func (rs *RegistrySynchronizer) handleUpkeepCancelled(broadcast log.Broadcast) { } broadcastedLog, ok := broadcast.DecodedLog().(*keeper_registry_wrapper.KeeperRegistryUpkeepCanceled) if !ok { - rs.logger.Errorf("invariant violation, expected UpkeepCanceled log but got %T", broadcastedLog) + rs.logger.AssumptionViolationf("expected UpkeepCanceled log but got %T", broadcastedLog) return } affected, err := rs.orm.BatchDeleteUpkeepsForJob(rs.job.ID, []int64{broadcastedLog.Id.Int64()}) @@ -107,7 +107,7 @@ func (rs *RegistrySynchronizer) handleUpkeepRegisteredLogs(done func()) { } broadcast, ok := i.(log.Broadcast) if !ok { - rs.logger.Errorf("invariant violation, expected log.Broadcast but got %T", broadcast) + rs.logger.AssumptionViolationf("expected log.Broadcast but got %T", broadcast) continue } rs.HandleUpkeepRegistered(broadcast, registry) @@ -127,7 +127,7 @@ func (rs *RegistrySynchronizer) HandleUpkeepRegistered(broadcast log.Broadcast, } broadcastedLog, ok := broadcast.DecodedLog().(*keeper_registry_wrapper.KeeperRegistryUpkeepRegistered) if !ok { - rs.logger.Errorf("invariant violation, expected UpkeepRegistered log but got %T", broadcastedLog) + rs.logger.AssumptionViolationf("expected UpkeepRegistered log but got %T", broadcastedLog) return } err = rs.syncUpkeep(registry, broadcastedLog.Id.Int64()) @@ -149,7 +149,7 @@ func (rs *RegistrySynchronizer) handleUpkeepPerformedLogs(done func()) { } broadcast, ok := i.(log.Broadcast) if !ok { - rs.logger.Errorf("invariant violation, expected log.Broadcast but got %T", broadcast) + rs.logger.AssumptionViolationf("expected log.Broadcast but got %T", broadcast) continue } rs.handleUpkeepPerformed(broadcast) @@ -171,7 +171,7 @@ func (rs *RegistrySynchronizer) handleUpkeepPerformed(broadcast log.Broadcast) { log, ok := broadcast.DecodedLog().(*keeper_registry_wrapper.KeeperRegistryUpkeepPerformed) if !ok { - rs.logger.Errorf("invariant violation, expected UpkeepPerformed log but got %T", log) + rs.logger.AssumptionViolationf("expected UpkeepPerformed log but got %T", log) return } diff --git a/core/services/keeper/registry_synchronizer_test.go b/core/services/keeper/registry_synchronizer_test.go index f1e5d07d03f..93741bd7caa 100644 --- a/core/services/keeper/registry_synchronizer_test.go +++ b/core/services/keeper/registry_synchronizer_test.go @@ -13,10 +13,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" "github.com/smartcontractkit/chainlink/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/keeper_registry_wrapper" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -80,7 +80,7 @@ func setupRegistrySync(t *testing.T) ( })).Return(func() {}) lbMock.On("IsConnected").Return(true).Maybe() - orm := keeper.NewORM(db, logger.TestLogger(t), ch.Config(), bulletprooftxmanager.SendEveryStrategy{}) + orm := keeper.NewORM(db, logger.TestLogger(t), ch.Config(), txmgr.SendEveryStrategy{}) synchronizer := keeper.NewRegistrySynchronizer(keeper.RegistrySynchronizerOptions{ Job: j, Contract: contract, @@ -117,13 +117,13 @@ func Test_RegistrySynchronizer_Start(t *testing.T) { registryMock.MockResponse("getCanceledUpkeepList", canceledUpkeeps).Once() registryMock.MockResponse("getUpkeepCount", big.NewInt(0)).Once() - err := synchronizer.Start() + err := synchronizer.Start(testutils.Context(t)) require.NoError(t, err) defer synchronizer.Close() cltest.WaitForCount(t, db, "keeper_registries", 1) - err = synchronizer.Start() + err = synchronizer.Start(testutils.Context(t)) require.Error(t, err) } @@ -197,7 +197,7 @@ func Test_RegistrySynchronizer_ConfigSetLog(t *testing.T) { registryMock.MockResponse("getCanceledUpkeepList", []*big.Int{}).Once() registryMock.MockResponse("getUpkeepCount", big.NewInt(0)).Once() - require.NoError(t, synchronizer.Start()) + require.NoError(t, synchronizer.Start(testutils.Context(t))) defer synchronizer.Close() cltest.WaitForCount(t, db, "keeper_registries", 1) var registry keeper.Registry @@ -241,7 +241,7 @@ func Test_RegistrySynchronizer_KeepersUpdatedLog(t *testing.T) { registryMock.MockResponse("getCanceledUpkeepList", []*big.Int{}).Once() registryMock.MockResponse("getUpkeepCount", big.NewInt(0)).Once() - require.NoError(t, synchronizer.Start()) + require.NoError(t, synchronizer.Start(testutils.Context(t))) defer synchronizer.Close() cltest.WaitForCount(t, db, "keeper_registries", 1) var registry keeper.Registry @@ -286,7 +286,7 @@ func Test_RegistrySynchronizer_UpkeepCanceledLog(t *testing.T) { registryMock.MockResponse("getUpkeepCount", big.NewInt(3)).Once() registryMock.MockResponse("getUpkeep", upkeepConfig).Times(3) - require.NoError(t, synchronizer.Start()) + require.NoError(t, synchronizer.Start(testutils.Context(t))) defer func() { require.NoError(t, synchronizer.Close()) }() cltest.WaitForCount(t, db, "keeper_registries", 1) cltest.WaitForCount(t, db, "upkeep_registrations", 3) @@ -322,7 +322,7 @@ func Test_RegistrySynchronizer_UpkeepRegisteredLog(t *testing.T) { registryMock.MockResponse("getCanceledUpkeepList", []*big.Int{}).Once() registryMock.MockResponse("getUpkeepCount", big.NewInt(0)).Once() - require.NoError(t, synchronizer.Start()) + require.NoError(t, synchronizer.Start(testutils.Context(t))) defer synchronizer.Close() cltest.WaitForCount(t, db, "keeper_registries", 1) @@ -362,7 +362,7 @@ func Test_RegistrySynchronizer_UpkeepPerformedLog(t *testing.T) { registryMock.MockResponse("getUpkeepCount", big.NewInt(1)).Once() registryMock.MockResponse("getUpkeep", upkeepConfig).Once() - require.NoError(t, synchronizer.Start()) + require.NoError(t, synchronizer.Start(testutils.Context(t))) defer synchronizer.Close() cltest.WaitForCount(t, db, "keeper_registries", 1) cltest.WaitForCount(t, db, "upkeep_registrations", 1) diff --git a/core/services/keeper/upkeep_executer.go b/core/services/keeper/upkeep_executer.go index 217ecbec12d..9aa633d99b4 100644 --- a/core/services/keeper/upkeep_executer.go +++ b/core/services/keeper/upkeep_executer.go @@ -30,7 +30,7 @@ const ( // UpkeepExecuter fulfills Service and HeadTrackable interfaces var ( - _ job.Service = (*UpkeepExecuter)(nil) + _ job.ServiceCtx = (*UpkeepExecuter)(nil) _ httypes.HeadTrackable = (*UpkeepExecuter)(nil) ) @@ -87,7 +87,7 @@ func NewUpkeepExecuter( } // Start starts the upkeep executer logic -func (ex *UpkeepExecuter) Start() error { +func (ex *UpkeepExecuter) Start(context.Context) error { return ex.StartOnce("UpkeepExecuter", func() error { ex.wgDone.Add(2) go ex.run() @@ -161,18 +161,18 @@ func (ex *UpkeepExecuter) processActiveUpkeeps() { } for _, reg := range activeUpkeeps { ex.executionQueue <- struct{}{} - go ex.execute(reg, head.Number, done) + go ex.execute(reg, head, done) } wg.Wait() } // execute triggers the pipeline run -func (ex *UpkeepExecuter) execute(upkeep UpkeepRegistration, headNumber int64, done func()) { +func (ex *UpkeepExecuter) execute(upkeep UpkeepRegistration, head *evmtypes.Head, done func()) { defer done() start := time.Now() - svcLogger := ex.logger.With("blockNum", headNumber, "upkeepID", upkeep.UpkeepID) + svcLogger := ex.logger.With("blockNum", head.Number, "upkeepID", upkeep.UpkeepID) svcLogger.Debug("checking upkeep") ctxService, cancel := utils.ContextFromChanWithDeadline(ex.chStop, time.Minute) @@ -187,10 +187,20 @@ func (ex *UpkeepExecuter) execute(upkeep UpkeepRegistration, headNumber int64, d if ex.config.KeeperCheckUpkeepGasPriceFeatureEnabled() { price, fee, err := ex.estimateGasPrice(upkeep) if err != nil { - svcLogger.Error(errors.Wrap(err, "estimating gas price")) + svcLogger.With("error", err).Error("estimating gas price") return } gasPrice, gasTipCap, gasFeeCap = price, fee.TipCap, fee.FeeCap + + // Make sure the gas price is at least as large as the basefee to avoid ErrFeeCapTooLow error from geth during eth call. + // If head.BaseFeePerGas, we assume it is a EIP-1559 chain. + // Note: gasPrice will be nil if EvmEIP1559DynamicFees is enabled. + if head.BaseFeePerGas != nil && head.BaseFeePerGas.ToInt().BitLen() > 0 { + baseFee := addBuffer(head.BaseFeePerGas.ToInt(), ex.config.KeeperBaseFeeBufferPercent()) + if gasPrice == nil || gasPrice.Cmp(baseFee) < 0 { + gasPrice = baseFee + } + } } vars := pipeline.NewVarsFrom(map[string]interface{}{ @@ -211,15 +221,15 @@ func (ex *UpkeepExecuter) execute(upkeep UpkeepRegistration, headNumber int64, d run := pipeline.NewRun(*ex.job.PipelineSpec, vars) if _, err := ex.pr.Run(ctxService, &run, svcLogger, true, nil); err != nil { - svcLogger.With("error", err).Errorw("failed executing run") + svcLogger.With("error", err).Error("failed executing run") return } // Only after task runs where a tx was broadcast if run.State == pipeline.RunStatusCompleted { - err := ex.orm.SetLastRunHeightForUpkeepOnJob(ex.job.ID, upkeep.UpkeepID, headNumber, pg.WithParentCtx(ctxService)) + err := ex.orm.SetLastRunHeightForUpkeepOnJob(ex.job.ID, upkeep.UpkeepID, head.Number, pg.WithParentCtx(ctxService)) if err != nil { - svcLogger.With("error", err).Errorw("failed to set last run height for upkeep") + svcLogger.With("error", err).Error("failed to set last run height for upkeep") } elapsed := time.Since(start) @@ -239,6 +249,7 @@ func (ex *UpkeepExecuter) estimateGasPrice(upkeep UpkeepRegistration) (gasPrice if err != nil { return nil, fee, errors.Wrap(err, "unable to construct performUpkeep data") } + if ex.config.EvmEIP1559DynamicFees() { fee, _, err = ex.gasEstimator.GetDynamicFee(upkeep.ExecuteGas) fee.TipCap = addBuffer(fee.TipCap, ex.config.KeeperGasTipCapBufferPercent()) @@ -249,6 +260,7 @@ func (ex *UpkeepExecuter) estimateGasPrice(upkeep UpkeepRegistration) (gasPrice if err != nil { return nil, fee, errors.Wrap(err, "unable to estimate gas") } + return gasPrice, fee, nil } diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index 067b9f21bf1..2985ce6ba4a 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" + "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -17,12 +18,14 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/core/chains/evm/gas/mocks" evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -32,7 +35,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/utils" bigmath "github.com/smartcontractkit/chainlink/core/utils/big_math" - "github.com/smartcontractkit/sqlx" ) func newHead() evmtypes.Head { @@ -48,7 +50,7 @@ func setup(t *testing.T) ( keeper.UpkeepRegistration, job.Job, cltest.JobPipelineV2TestHelper, - *bptxmmocks.TxManager, + *txmmocks.TxManager, keystore.Master, evm.Chain, keeper.ORM, @@ -59,21 +61,25 @@ func setup(t *testing.T) ( db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, cfg) ethClient := cltest.NewEthClientMockWithDefaultChain(t) - txm := new(bptxmmocks.TxManager) + txm := new(txmmocks.TxManager) txm.Test(t) estimator := new(gasmocks.Estimator) estimator.Test(t) txm.On("GetGasEstimator").Return(estimator) estimator.On("GetLegacyGas", mock.Anything, mock.Anything).Maybe().Return(assets.GWei(60), uint64(0), nil) + estimator.On("GetDynamicFee", mock.Anything).Maybe().Return(gas.DynamicFee{ + FeeCap: assets.GWei(60), + TipCap: assets.GWei(60), + }, uint64(60), nil) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{TxManager: txm, DB: db, Client: ethClient, KeyStore: keyStore.Eth(), GeneralConfig: cfg}) jpv2 := cltest.NewJobPipelineV2(t, cfg, cc, db, keyStore) ch := evmtest.MustGetDefaultChain(t, cc) - orm := keeper.NewORM(db, logger.TestLogger(t), ch.Config(), bulletprooftxmanager.SendEveryStrategy{}) + orm := keeper.NewORM(db, logger.TestLogger(t), ch.Config(), txmgr.SendEveryStrategy{}) registry, job := cltest.MustInsertKeeperRegistry(t, db, orm, keyStore.Eth()) lggr := logger.TestLogger(t) executer := keeper.NewUpkeepExecuter(job, orm, jpv2.Pr, ethClient, ch.HeadBroadcaster(), ch.TxManager().GetGasEstimator(), lggr, ch.Config()) upkeep := cltest.MustInsertUpkeepForRegistry(t, db, ch.Config(), registry) - err := executer.Start() + err := executer.Start(testutils.Context(t)) t.Cleanup(func() { txm.AssertExpectations(t); estimator.AssertExpectations(t); executer.Close() }) require.NoError(t, err) return db, cfg, ethClient, executer, registry, upkeep, job, jpv2, txm, keyStore, ch, orm @@ -96,7 +102,7 @@ var checkUpkeepResponse = struct { func Test_UpkeepExecuter_ErrorsIfStartedTwice(t *testing.T) { t.Parallel() _, _, _, executer, _, _, _, _, _, _, _, _ := setup(t) - err := executer.Start() // already started in setup() + err := executer.Start(testutils.Context(t)) // already started in setup() require.Error(t, err) } @@ -111,10 +117,10 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { ethTxCreated := cltest.NewAwaiter() txm.On("CreateEthTransaction", - mock.MatchedBy(func(newTx bulletprooftxmanager.NewTx) bool { return newTx.GasLimit == gasLimit }), + mock.MatchedBy(func(newTx txmgr.NewTx) bool { return newTx.GasLimit == gasLimit }), ). Once(). - Return(bulletprooftxmanager.EthTx{ + Return(txmgr.EthTx{ ID: 1, }, nil). Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) @@ -142,6 +148,65 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { txm.AssertExpectations(t) }) + t.Run("runs upkeep on triggering block number on EIP1559 and non-EIP1559 chains", func(t *testing.T) { + runTest := func(t *testing.T, eip1559 bool) { + db, config, ethMock, executer, registry, upkeep, job, jpv2, txm, _, _, _ := setup(t) + + config.Overrides.GlobalEvmEIP1559DynamicFees = null.BoolFrom(eip1559) + + gasLimit := upkeep.ExecuteGas + config.KeeperRegistryPerformGasOverhead() + gasPrice := bigmath.Div(bigmath.Mul(assets.GWei(60), 100+config.KeeperGasPriceBufferPercent()), 100) + baseFeePerGas := utils.NewBig(big.NewInt(0).Mul(gasPrice, big.NewInt(2))) + + ethTxCreated := cltest.NewAwaiter() + txm.On("CreateEthTransaction", + mock.MatchedBy(func(newTx txmgr.NewTx) bool { return newTx.GasLimit == gasLimit }), + ). + Once(). + Return(txmgr.EthTx{ + ID: 1, + }, nil). + Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) + + registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.RegistryABI, registry.ContractAddress.Address()) + registryMock.MockMatchedResponse( + "checkUpkeep", + func(callArgs ethereum.CallMsg) bool { + expectedGasPrice := bigmath.Div( + bigmath.Mul(baseFeePerGas.ToInt(), 100+config.KeeperBaseFeeBufferPercent()), + 100, + ) + + return bigmath.Equal(callArgs.GasPrice, expectedGasPrice) && + 650_000 == callArgs.Gas + }, + checkUpkeepResponse, + ) + + head := newHead() + head.BaseFeePerGas = baseFeePerGas + + executer.OnNewLongestChain(context.Background(), &head) + ethTxCreated.AwaitOrFail(t) + runs := cltest.WaitForPipelineComplete(t, 0, job.ID, 1, 5, jpv2.Jrm, time.Second, 100*time.Millisecond) + require.Len(t, runs, 1) + assert.False(t, runs[0].HasErrors()) + assert.False(t, runs[0].HasFatalErrors()) + waitLastRunHeight(t, db, upkeep, 20) + + ethMock.AssertExpectations(t) + txm.AssertExpectations(t) + } + + t.Run("EIP1559", func(t *testing.T) { + runTest(t, true) + }) + + t.Run("non-EIP1559", func(t *testing.T) { + runTest(t, false) + }) + }) + t.Run("errors if submission key not found", func(t *testing.T) { _, config, ethMock, executer, registry, _, job, jpv2, _, keyStore, _, _ := setup(t) @@ -180,7 +245,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { job.KeeperSpec.EVMChainID = (*utils.Big)(big.NewInt(999)) lggr := logger.TestLogger(t) executer := keeper.NewUpkeepExecuter(job, orm, jpv2.Pr, ethMock, ch.HeadBroadcaster(), ch.TxManager().GetGasEstimator(), lggr, ch.Config()) - err := executer.Start() + err := executer.Start(testutils.Context(t)) require.NoError(t, err) head := newHead() executer.OnNewLongestChain(context.Background(), &head) @@ -199,10 +264,10 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { } gasLimit := upkeep.ExecuteGas + config.KeeperRegistryPerformGasOverhead() txm.On("CreateEthTransaction", - mock.MatchedBy(func(newTx bulletprooftxmanager.NewTx) bool { return newTx.GasLimit == gasLimit }), + mock.MatchedBy(func(newTx txmgr.NewTx) bool { return newTx.GasLimit == gasLimit }), ). Once(). - Return(bulletprooftxmanager.EthTx{}, nil). + Return(txmgr.EthTx{}, nil). Run(func(mock.Arguments) { etxs[0].ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.RegistryABI, registry.ContractAddress.Address()) @@ -229,10 +294,10 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { head = cltest.Head(40) txm.On("CreateEthTransaction", - mock.MatchedBy(func(newTx bulletprooftxmanager.NewTx) bool { return newTx.GasLimit == gasLimit }), + mock.MatchedBy(func(newTx txmgr.NewTx) bool { return newTx.GasLimit == gasLimit }), ). Once(). - Return(bulletprooftxmanager.EthTx{}, nil). + Return(txmgr.EthTx{}, nil). Run(func(mock.Arguments) { etxs[1].ItHappened() }) executer.OnNewLongestChain(context.Background(), head) @@ -259,7 +324,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Error(t *testing.T) { }) head := newHead() - executer.OnNewLongestChain(context.TODO(), &head) + executer.OnNewLongestChain(testutils.Context(t), &head) g.Eventually(wasCalled.Load).Should(gomega.Equal(true)) cltest.AssertCountStays(t, db, "eth_txes", 0) diff --git a/core/services/keeper/validate_test.go b/core/services/keeper/validate_test.go index fe592e7329d..e26d12dac43 100644 --- a/core/services/keeper/validate_test.go +++ b/core/services/keeper/validate_test.go @@ -33,6 +33,7 @@ func TestValidatedKeeperSpec(t *testing.T) { name: "valid job spec", args: args{ tomlString: testspecs.GenerateKeeperSpec(testspecs.KeeperSpecParams{ + Name: "example keeper spec", ContractAddress: "0x9E40733cC9df84636505f4e6Db28DCa0dC5D1bba", FromAddress: "0xa8037A20989AFcBC51798de9762b351D63ff462e", }).Toml(), diff --git a/core/services/keystore/keys/ocr2key/terra_keyring.go b/core/services/keystore/keys/ocr2key/terra_keyring.go index 6ac79cc6ace..28618a543cd 100644 --- a/core/services/keystore/keys/ocr2key/terra_keyring.go +++ b/core/services/keystore/keys/ocr2key/terra_keyring.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "io" + "github.com/pkg/errors" + "github.com/hdevalence/ed25519consensus" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" @@ -84,8 +86,11 @@ func (tk *terraKeyring) marshal() ([]byte, error) { } func (tk *terraKeyring) unmarshal(in []byte) error { + if len(in) != ed25519.SeedSize { + return errors.Errorf("unexpected seed size, got %d want %d", len(in), ed25519.SeedSize) + } privKey := ed25519.NewKeyFromSeed(in) tk.privKey = privKey - tk.pubKey = ed25519.PublicKey(privKey[32:]) + tk.pubKey = privKey.Public().(ed25519.PublicKey) return nil } diff --git a/core/services/keystore/keys/ocr2key/terra_keyring_test.go b/core/services/keystore/keys/ocr2key/terra_keyring_test.go index 109929490ed..b239c037ab1 100644 --- a/core/services/keystore/keys/ocr2key/terra_keyring_test.go +++ b/core/services/keystore/keys/ocr2key/terra_keyring_test.go @@ -1,9 +1,12 @@ package ocr2key import ( + "bytes" cryptorand "crypto/rand" "testing" + "github.com/stretchr/testify/assert" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/stretchr/testify/require" ) @@ -38,3 +41,18 @@ func TestTerraKeyRing_Sign_Verify(t *testing.T) { require.False(t, result) }) } + +func TestTerraKeyRing_Marshalling(t *testing.T) { + kr1, err := newTerraKeyring(cryptorand.Reader) + require.NoError(t, err) + m, err := kr1.marshal() + require.NoError(t, err) + kr2 := terraKeyring{} + err = kr2.unmarshal(m) + require.NoError(t, err) + assert.True(t, bytes.Equal(kr1.pubKey, kr2.pubKey)) + assert.True(t, bytes.Equal(kr1.privKey, kr2.privKey)) + + // Invalid seed size should error + require.Error(t, kr2.unmarshal([]byte{0x01})) +} diff --git a/core/services/keystore/keys/vrfkey/key_v2.go b/core/services/keystore/keys/vrfkey/key_v2.go index fc6f50117a3..7f742fe0e30 100644 --- a/core/services/keystore/keys/vrfkey/key_v2.go +++ b/core/services/keystore/keys/vrfkey/key_v2.go @@ -121,7 +121,7 @@ func (key KeyV2) GenerateProof(seed *big.Int) (Proof, error) { } proof, err := key.GenerateProofWithNonce(seed, nonce) switch { - case err == ErrCGammaEqualsSHash: + case errors.Is(err, ErrCGammaEqualsSHash): // This is cryptographically impossible, but if it were ever to happen, we // should try again with a different nonce. continue diff --git a/core/services/offchainreporting/config.go b/core/services/ocr/config.go similarity index 78% rename from core/services/offchainreporting/config.go rename to core/services/ocr/config.go index 632fb4c92db..3ab45fa4b61 100644 --- a/core/services/offchainreporting/config.go +++ b/core/services/ocr/config.go @@ -1,11 +1,11 @@ -package offchainreporting +package ocr import ( "github.com/smartcontractkit/chainlink/core/services/job" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ) -func toLocalConfig(cfg ValidationConfig, spec job.OffchainReportingOracleSpec) ocrtypes.LocalConfig { +func toLocalConfig(cfg ValidationConfig, spec job.OCROracleSpec) ocrtypes.LocalConfig { concreteSpec := job.LoadEnvConfigVarsLocalOCR(cfg, spec) lc := ocrtypes.LocalConfig{ BlockchainTimeout: concreteSpec.BlockchainTimeout.Duration(), @@ -13,10 +13,10 @@ func toLocalConfig(cfg ValidationConfig, spec job.OffchainReportingOracleSpec) o SkipContractConfigConfirmations: cfg.ChainType().IsL2(), ContractConfigTrackerPollInterval: concreteSpec.ContractConfigTrackerPollInterval.Duration(), ContractConfigTrackerSubscribeInterval: concreteSpec.ContractConfigTrackerSubscribeInterval.Duration(), - ContractTransmitterTransmitTimeout: cfg.OCRContractTransmitterTransmitTimeout(), + ContractTransmitterTransmitTimeout: concreteSpec.ContractTransmitterTransmitTimeout.Duration(), DatabaseTimeout: concreteSpec.DatabaseTimeout.Duration(), DataSourceTimeout: concreteSpec.ObservationTimeout.Duration(), - DataSourceGracePeriod: cfg.OCRObservationGracePeriod(), + DataSourceGracePeriod: concreteSpec.ObservationGracePeriod.Duration(), } if cfg.Dev() { // Skips config validation so we can use any config parameters we want. diff --git a/core/services/offchainreporting/config_overrider.go b/core/services/ocr/config_overrider.go similarity index 97% rename from core/services/offchainreporting/config_overrider.go rename to core/services/ocr/config_overrider.go index 8b2f702606b..62775e37200 100644 --- a/core/services/offchainreporting/config_overrider.go +++ b/core/services/ocr/config_overrider.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "context" @@ -9,10 +9,11 @@ import ( "time" "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/utils" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ) type ConfigOverriderImpl struct { @@ -72,7 +73,8 @@ func NewConfigOverriderImpl( return &co, nil } -func (c *ConfigOverriderImpl) Start() error { +// Start starts ConfigOverriderImpl. +func (c *ConfigOverriderImpl) Start(context.Context) error { return c.StartOnce("OCRConfigOverrider", func() (err error) { if err := c.updateFlagsStatus(); err != nil { c.logger.Errorw("OCRConfigOverrider: Error updating hibernation status at OCR job start. Will default to not hibernating, until next successful update.", "err", err) diff --git a/core/services/offchainreporting/config_overrider_test.go b/core/services/ocr/config_overrider_test.go similarity index 86% rename from core/services/offchainreporting/config_overrider_test.go rename to core/services/ocr/config_overrider_test.go index b28ab6c7305..d16c7615b13 100644 --- a/core/services/offchainreporting/config_overrider_test.go +++ b/core/services/ocr/config_overrider_test.go @@ -1,4 +1,4 @@ -package offchainreporting_test +package ocr_test import ( "math" @@ -8,19 +8,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/utils" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) type configOverriderUni struct { - overrider *offchainreporting.ConfigOverriderImpl + overrider *ocr.ConfigOverriderImpl contractAddress ethkey.EIP55Address } @@ -28,9 +30,9 @@ func newConfigOverriderUni(t *testing.T, pollITicker utils.TickerBase, flagsCont var testLogger = logger.TestLogger(t) contractAddress := cltest.NewEIP55Address() - flags := &offchainreporting.ContractFlags{FlagsInterface: flagsContract} + flags := &ocr.ContractFlags{FlagsInterface: flagsContract} var err error - uni.overrider, err = offchainreporting.NewConfigOverriderImpl( + uni.overrider, err = ocr.NewConfigOverriderImpl( testLogger, contractAddress, flags, @@ -66,7 +68,7 @@ func TestIntegration_OCRConfigOverrider_EntersHibernation(t *testing.T) { Run(checkFlagsAddress(t, uni.contractAddress)). Return([]bool{true, true}, nil) - require.NoError(t, uni.overrider.Start()) + require.NoError(t, uni.overrider.Start(testutils.Context(t))) // not hibernating initially require.Nil(t, uni.overrider.ConfigOverride()) @@ -97,7 +99,7 @@ func Test_OCRConfigOverrider(t *testing.T) { Run(checkFlagsAddress(t, uni.contractAddress)). Return([]bool{true, true}, nil) - require.NoError(t, uni.overrider.Start()) + require.NoError(t, uni.overrider.Start(testutils.Context(t))) // not hibernating initially require.Nil(t, uni.overrider.ConfigOverride()) @@ -126,7 +128,7 @@ func Test_OCRConfigOverrider(t *testing.T) { Run(checkFlagsAddress(t, uni.contractAddress)). Return([]bool{true, false}, nil) - require.NoError(t, uni.overrider.Start()) + require.NoError(t, uni.overrider.Start(testutils.Context(t))) // initially enters hibernation expectedOverride := &ocrtypes.ConfigOverride{AlphaPPB: math.MaxUint64, DeltaC: uni.overrider.DeltaCFromAddress} @@ -142,8 +144,8 @@ func Test_OCRConfigOverrider(t *testing.T) { t.Run("Errors if flags contract is missing", func(t *testing.T) { var testLogger = logger.TestLogger(t) contractAddress := cltest.NewEIP55Address() - flags := &offchainreporting.ContractFlags{FlagsInterface: nil} - _, err := offchainreporting.NewConfigOverriderImpl( + flags := &ocr.ContractFlags{FlagsInterface: nil} + _, err := ocr.NewConfigOverriderImpl( testLogger, contractAddress, flags, @@ -159,7 +161,7 @@ func Test_OCRConfigOverrider(t *testing.T) { flagsContract.Test(t) flagsContract.On("GetFlags", mock.Anything, mock.Anything). Return([]bool{true, true}, nil) - flags := &offchainreporting.ContractFlags{FlagsInterface: flagsContract} + flags := &ocr.ContractFlags{FlagsInterface: flagsContract} address1, err := ethkey.NewEIP55Address(common.BigToAddress(big.NewInt(10000)).Hex()) require.NoError(t, err) @@ -167,13 +169,13 @@ func Test_OCRConfigOverrider(t *testing.T) { address2, err := ethkey.NewEIP55Address(common.BigToAddress(big.NewInt(1234567890)).Hex()) require.NoError(t, err) - overrider1a, err := offchainreporting.NewConfigOverriderImpl(testLogger, address1, flags, nil) + overrider1a, err := ocr.NewConfigOverriderImpl(testLogger, address1, flags, nil) require.NoError(t, err) - overrider1b, err := offchainreporting.NewConfigOverriderImpl(testLogger, address1, flags, nil) + overrider1b, err := ocr.NewConfigOverriderImpl(testLogger, address1, flags, nil) require.NoError(t, err) - overrider2, err := offchainreporting.NewConfigOverriderImpl(testLogger, address2, flags, nil) + overrider2, err := ocr.NewConfigOverriderImpl(testLogger, address2, flags, nil) require.NoError(t, err) require.Equal(t, overrider1a.DeltaCFromAddress, time.Duration(85600000000000)) diff --git a/core/services/offchainreporting/contract_config_subscription.go b/core/services/ocr/contract_config_subscription.go similarity index 96% rename from core/services/offchainreporting/contract_config_subscription.go rename to core/services/ocr/contract_config_subscription.go index abaa8fe5042..9aaea3c3ec3 100644 --- a/core/services/offchainreporting/contract_config_subscription.go +++ b/core/services/ocr/contract_config_subscription.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" diff --git a/core/services/offchainreporting/contract_tracker.go b/core/services/ocr/contract_tracker.go similarity index 97% rename from core/services/offchainreporting/contract_tracker.go rename to core/services/ocr/contract_tracker.go index d3c4f94b982..5002d5657fd 100644 --- a/core/services/offchainreporting/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "context" @@ -15,6 +15,10 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + "github.com/smartcontractkit/libocr/offchainreporting/confighelper" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/core/chains" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" @@ -25,9 +29,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - "github.com/smartcontractkit/libocr/offchainreporting/confighelper" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ) // configMailboxSanityLimit is the maximum number of configs that can be held @@ -69,8 +70,7 @@ type ( unsubscribeHeads func() // Start/Stop lifecycle - ctx context.Context - ctxCancel context.CancelFunc + chStop chan struct{} wg sync.WaitGroup unsubscribeLogs func() @@ -107,7 +107,6 @@ func NewOCRContractTracker( cfg ocrcommon.Config, headBroadcaster httypes.HeadBroadcaster, ) (o *OCRContractTracker) { - ctx, cancel := context.WithCancel(context.Background()) logger = logger.Named("OCRContractTracker") return &OCRContractTracker{ utils.StartStopOnce{}, @@ -124,8 +123,7 @@ func NewOCRContractTracker( cfg, headBroadcaster, nil, - ctx, - cancel, + make(chan struct{}), sync.WaitGroup{}, nil, offchainaggregator.OffchainAggregatorRoundRequested{}, @@ -139,7 +137,7 @@ func NewOCRContractTracker( // Start must be called before logs can be delivered // It ought to be called before starting OCR -func (t *OCRContractTracker) Start() error { +func (t *OCRContractTracker) Start(context.Context) error { return t.StartOnce("OCRContractTracker", func() (err error) { t.latestRoundRequested, err = t.ocrDB.LoadLatestRoundRequested() if err != nil { @@ -171,7 +169,7 @@ func (t *OCRContractTracker) Start() error { // Close should be called after teardown of the OCR job relying on this tracker func (t *OCRContractTracker) Close() error { return t.StopOnce("OCRContractTracker", func() error { - t.ctxCancel() + close(t.chStop) t.wg.Wait() t.unsubscribeHeads() t.unsubscribeLogs() @@ -224,11 +222,11 @@ func (t *OCRContractTracker) processLogs() { } select { case t.chConfigs <- cc: - case <-t.ctx.Done(): + case <-t.chStop: return } } - case <-t.ctx.Done(): + case <-t.chStop: return } } @@ -340,7 +338,7 @@ func (t *OCRContractTracker) SubscribeToNewConfigs(context.Context) (ocrtypes.Co // LatestConfigDetails queries the eth node func (t *OCRContractTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) + ctx, cancel = utils.WithCloseChan(ctx, t.chStop) defer cancel() opts := bind.CallOpts{Context: ctx, Pending: false} @@ -368,7 +366,7 @@ func (t *OCRContractTracker) ConfigFromLogs(ctx context.Context, changedInBlock } var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) + ctx, cancel = utils.WithCloseChan(ctx, t.chStop) defer cancel() logs, err := t.ethClient.FilterLogs(ctx, q) @@ -406,7 +404,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight t.logger.Debugw("still waiting for first head, falling back to on-chain lookup") var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) + ctx, cancel = utils.WithCloseChan(ctx, t.chStop) defer cancel() h, err := t.ethClient.HeadByNumber(ctx, nil) diff --git a/core/services/offchainreporting/contract_tracker_test.go b/core/services/ocr/contract_tracker_test.go similarity index 97% rename from core/services/offchainreporting/contract_tracker_test.go rename to core/services/ocr/contract_tracker_test.go index 0ab62c50b3d..6d2b6a2eeb9 100644 --- a/core/services/offchainreporting/contract_tracker_test.go +++ b/core/services/ocr/contract_tracker_test.go @@ -1,4 +1,4 @@ -package offchainreporting_test +package ocr_test import ( "context" @@ -8,6 +8,12 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" htmocks "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/mocks" logmocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" @@ -20,13 +26,8 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" - ocrmocks "github.com/smartcontractkit/chainlink/core/services/offchainreporting/mocks" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/services/ocr" + ocrmocks "github.com/smartcontractkit/chainlink/core/services/ocr/mocks" ) func mustNewContract(t *testing.T, address gethCommon.Address) *offchain_aggregator_wrapper.OffchainAggregator { @@ -46,7 +47,7 @@ type contractTrackerUni struct { lb *logmocks.Broadcaster hb *htmocks.HeadBroadcaster ec *evmmocks.Client - tracker *offchainreporting.OCRContractTracker + tracker *ocr.OCRContractTracker } func newContractTrackerUni(t *testing.T, opts ...interface{}) (uni contractTrackerUni) { @@ -80,7 +81,7 @@ func newContractTrackerUni(t *testing.T, opts ...interface{}) (uni contractTrack uni.ec = cltest.NewEthClientMock(t) db := pgtest.NewSqlxDB(t) - uni.tracker = offchainreporting.NewOCRContractTracker( + uni.tracker = ocr.NewOCRContractTracker( contract, filterer, nil, @@ -158,7 +159,7 @@ func Test_OCRContractTracker_LatestBlockHeight(t *testing.T) { uni.db.On("LoadLatestRoundRequested").Return(offchainaggregator.OffchainAggregatorRoundRequested{}, nil) uni.lb.On("Register", uni.tracker, mock.Anything).Return(func() {}) - require.NoError(t, uni.tracker.Start()) + require.NoError(t, uni.tracker.Start(testutils.Context(t))) l, err := uni.tracker.LatestBlockHeight(context.Background()) require.NoError(t, err) @@ -363,7 +364,7 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin uni.db.On("LoadLatestRoundRequested").Return(rr, nil) - require.NoError(t, uni.tracker.Start()) + require.NoError(t, uni.tracker.Start(testutils.Context(t))) configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) @@ -437,7 +438,7 @@ func Test_OCRContractTracker_IsLaterThan(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - res := offchainreporting.IsLaterThan(test.incoming, test.existing) + res := ocr.IsLaterThan(test.incoming, test.existing) assert.Equal(t, test.expected, res) }) } diff --git a/core/services/offchainreporting/contract_transmitter.go b/core/services/ocr/contract_transmitter.go similarity index 99% rename from core/services/offchainreporting/contract_transmitter.go rename to core/services/ocr/contract_transmitter.go index 5d42b19f839..f544454acd6 100644 --- a/core/services/offchainreporting/contract_transmitter.go +++ b/core/services/ocr/contract_transmitter.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "context" diff --git a/core/services/offchainreporting/contract_transmitter_test.go b/core/services/ocr/contract_transmitter_test.go similarity index 80% rename from core/services/offchainreporting/contract_transmitter_test.go rename to core/services/ocr/contract_transmitter_test.go index 2312024eff4..52e30dc4e02 100644 --- a/core/services/offchainreporting/contract_transmitter_test.go +++ b/core/services/ocr/contract_transmitter_test.go @@ -1,4 +1,4 @@ -package offchainreporting_test +package ocr_test import ( "math/big" @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/smartcontractkit/chainlink/core/internal/testutils" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,7 +17,7 @@ func Test_ContractTransmitter_ChainID(t *testing.T) { chainID := big.NewInt(42) contractABI, err := abi.JSON(strings.NewReader(offchainaggregator.OffchainAggregatorABI)) require.NoError(t, err) - ct := offchainreporting.NewOCRContractTransmitter( + ct := ocr.NewOCRContractTransmitter( testutils.NewAddress(), nil, contractABI, diff --git a/core/services/offchainreporting/database.go b/core/services/ocr/database.go similarity index 83% rename from core/services/offchainreporting/database.go rename to core/services/ocr/database.go index 1d15e3a2eb0..38905861b73 100644 --- a/core/services/offchainreporting/database.go +++ b/core/services/ocr/database.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "context" @@ -37,8 +37,8 @@ func NewDB(sqldb *sql.DB, oracleSpecID int32, lggr logger.Logger) *db { func (d *db) ReadState(ctx context.Context, cd ocrtypes.ConfigDigest) (ps *ocrtypes.PersistentState, err error) { q := d.QueryRowContext(ctx, ` SELECT epoch, highest_sent_epoch, highest_received_epoch -FROM offchainreporting_persistent_states -WHERE offchainreporting_oracle_spec_id = $1 AND config_digest = $2 +FROM ocr_persistent_states +WHERE ocr_oracle_spec_id = $1 AND config_digest = $2 LIMIT 1`, d.oracleSpecID, cd) ps = new(ocrtypes.PersistentState) @@ -65,9 +65,9 @@ func (d *db) WriteState(ctx context.Context, cd ocrtypes.ConfigDigest, state ocr highestReceivedEpoch = append(highestReceivedEpoch, int64(v)) } _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting_persistent_states (offchainreporting_oracle_spec_id, config_digest, epoch, highest_sent_epoch, highest_received_epoch, created_at, updated_at) +INSERT INTO ocr_persistent_states (ocr_oracle_spec_id, config_digest, epoch, highest_sent_epoch, highest_received_epoch, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) -ON CONFLICT (offchainreporting_oracle_spec_id, config_digest) DO UPDATE SET +ON CONFLICT (ocr_oracle_spec_id, config_digest) DO UPDATE SET (epoch, highest_sent_epoch, highest_received_epoch, updated_at) = ( @@ -84,8 +84,8 @@ ON CONFLICT (offchainreporting_oracle_spec_id, config_digest) DO UPDATE SET func (d *db) ReadConfig(ctx context.Context) (c *ocrtypes.ContractConfig, err error) { q := d.QueryRowContext(ctx, ` SELECT config_digest, signers, transmitters, threshold, encoded_config_version, encoded - FROM offchainreporting_contract_configs - WHERE offchainreporting_oracle_spec_id = $1 + FROM ocr_contract_configs + WHERE ocr_oracle_spec_id = $1 LIMIT 1`, d.oracleSpecID) c = new(ocrtypes.ContractConfig) @@ -120,9 +120,9 @@ func (d *db) WriteConfig(ctx context.Context, c ocrtypes.ContractConfig) error { transmitters = append(transmitters, t.Bytes()) } _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting_contract_configs (offchainreporting_oracle_spec_id, config_digest, signers, transmitters, threshold, encoded_config_version, encoded, created_at, updated_at) +INSERT INTO ocr_contract_configs (ocr_oracle_spec_id, config_digest, signers, transmitters, threshold, encoded_config_version, encoded, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) -ON CONFLICT (offchainreporting_oracle_spec_id) DO UPDATE SET +ON CONFLICT (ocr_oracle_spec_id) DO UPDATE SET config_digest = EXCLUDED.config_digest, signers = EXCLUDED.signers, transmitters = EXCLUDED.transmitters, @@ -151,8 +151,8 @@ func (d *db) StorePendingTransmission(ctx context.Context, k ocrtypes.PendingTra } _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting_pending_transmissions ( - offchainreporting_oracle_spec_id, +INSERT INTO ocr_pending_transmissions ( + ocr_oracle_spec_id, config_digest, epoch, round, @@ -166,7 +166,7 @@ INSERT INTO offchainreporting_pending_transmissions ( updated_at ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW(),NOW()) -ON CONFLICT (offchainreporting_oracle_spec_id, config_digest, epoch, round) DO UPDATE SET +ON CONFLICT (ocr_oracle_spec_id, config_digest, epoch, round) DO UPDATE SET time = EXCLUDED.time, median = EXCLUDED.median, serialized_report = EXCLUDED.serialized_report, @@ -191,13 +191,13 @@ SELECT rs, ss, vs -FROM offchainreporting_pending_transmissions -WHERE offchainreporting_oracle_spec_id = $1 AND config_digest = $2 +FROM ocr_pending_transmissions +WHERE ocr_oracle_spec_id = $1 AND config_digest = $2 `, d.oracleSpecID, cd) if err != nil { return nil, errors.Wrap(err, "PendingTransmissionsWithConfigDigest failed to query rows") } - defer d.lggr.ErrorIfClosing(rows, "offchainreporting_pending_transmissions rows") + defer d.lggr.ErrorIfClosing(rows, "ocr_pending_transmissions rows") m := make(map[ocrtypes.PendingTransmissionKey]ocrtypes.PendingTransmission) @@ -242,8 +242,8 @@ WHERE offchainreporting_oracle_spec_id = $1 AND config_digest = $2 func (d *db) DeletePendingTransmission(ctx context.Context, k ocrtypes.PendingTransmissionKey) (err error) { _, err = d.ExecContext(ctx, ` -DELETE FROM offchainreporting_pending_transmissions -WHERE offchainreporting_oracle_spec_id = $1 AND config_digest = $2 AND epoch = $3 AND round = $4 +DELETE FROM ocr_pending_transmissions +WHERE ocr_oracle_spec_id = $1 AND config_digest = $2 AND epoch = $3 AND round = $4 `, d.oracleSpecID, k.ConfigDigest, k.Epoch, k.Round) err = errors.Wrap(err, "DeletePendingTransmission failed") @@ -253,8 +253,8 @@ WHERE offchainreporting_oracle_spec_id = $1 AND config_digest = $2 AND epoch = func (d *db) DeletePendingTransmissionsOlderThan(ctx context.Context, t time.Time) (err error) { _, err = d.ExecContext(ctx, ` -DELETE FROM offchainreporting_pending_transmissions -WHERE offchainreporting_oracle_spec_id = $1 AND time < $2 +DELETE FROM ocr_pending_transmissions +WHERE ocr_oracle_spec_id = $1 AND time < $2 `, d.oracleSpecID, t) err = errors.Wrap(err, "DeletePendingTransmissionsOlderThan failed") @@ -268,8 +268,8 @@ func (d *db) SaveLatestRoundRequested(tx pg.Queryer, rr offchainaggregator.Offch return errors.Wrap(err, "could not marshal log as JSON") } _, err = tx.Exec(` -INSERT INTO offchainreporting_latest_round_requested (offchainreporting_oracle_spec_id, requester, config_digest, epoch, round, raw) -VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (offchainreporting_oracle_spec_id) DO UPDATE SET +INSERT INTO ocr_latest_round_requested (ocr_oracle_spec_id, requester, config_digest, epoch, round, raw) +VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (ocr_oracle_spec_id) DO UPDATE SET requester = EXCLUDED.requester, config_digest = EXCLUDED.config_digest, epoch = EXCLUDED.epoch, @@ -283,8 +283,8 @@ VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (offchainreporting_oracle_spec_id) DO UPD func (d *db) LoadLatestRoundRequested() (rr offchainaggregator.OffchainAggregatorRoundRequested, err error) { rows, err := d.Query(` SELECT requester, config_digest, epoch, round, raw -FROM offchainreporting_latest_round_requested -WHERE offchainreporting_oracle_spec_id = $1 +FROM ocr_latest_round_requested +WHERE ocr_oracle_spec_id = $1 LIMIT 1 `, d.oracleSpecID) if err != nil { diff --git a/core/services/offchainreporting/database_test.go b/core/services/ocr/database_test.go similarity index 93% rename from core/services/offchainreporting/database_test.go rename to core/services/ocr/database_test.go index e0c02d1fd4b..2a15fa0ff63 100644 --- a/core/services/offchainreporting/database_test.go +++ b/core/services/ocr/database_test.go @@ -1,4 +1,4 @@ -package offchainreporting_test +package ocr_test import ( "bytes" @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" @@ -33,7 +33,7 @@ func Test_DB_ReadWriteState(t *testing.T) { spec := cltest.MustInsertOffchainreportingOracleSpec(t, db, key.Address) t.Run("reads and writes state", func(t *testing.T) { - odb := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + odb := ocr.NewTestDB(t, sqlDB, spec.ID) state := ocrtypes.PersistentState{ Epoch: 1, HighestSentEpoch: 2, @@ -50,7 +50,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("updates state", func(t *testing.T) { - odb := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + odb := ocr.NewTestDB(t, sqlDB, spec.ID) newState := ocrtypes.PersistentState{ Epoch: 2, HighestSentEpoch: 3, @@ -67,7 +67,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("does not return result for wrong spec", func(t *testing.T) { - odb := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + odb := ocr.NewTestDB(t, sqlDB, spec.ID) state := ocrtypes.PersistentState{ Epoch: 3, HighestSentEpoch: 4, @@ -78,7 +78,7 @@ func Test_DB_ReadWriteState(t *testing.T) { require.NoError(t, err) // db with different spec - odb = offchainreporting.NewTestDB(t, sqlDB, -1) + odb = ocr.NewTestDB(t, sqlDB, -1) readState, err := odb.ReadState(ctx, configDigest) require.NoError(t, err) @@ -87,7 +87,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("does not return result for wrong config digest", func(t *testing.T) { - odb := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + odb := ocr.NewTestDB(t, sqlDB, spec.ID) state := ocrtypes.PersistentState{ Epoch: 4, HighestSentEpoch: 5, @@ -123,7 +123,7 @@ func Test_DB_ReadWriteConfig(t *testing.T) { transmitterAddress := key.Address.Address() t.Run("reads and writes config", func(t *testing.T) { - db := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + db := ocr.NewTestDB(t, sqlDB, spec.ID) err := db.WriteConfig(ctx, config) require.NoError(t, err) @@ -135,7 +135,7 @@ func Test_DB_ReadWriteConfig(t *testing.T) { }) t.Run("updates config", func(t *testing.T) { - db := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + db := ocr.NewTestDB(t, sqlDB, spec.ID) newConfig := ocrtypes.ContractConfig{ ConfigDigest: cltest.MakeConfigDigest(t), @@ -156,12 +156,12 @@ func Test_DB_ReadWriteConfig(t *testing.T) { }) t.Run("does not return result for wrong spec", func(t *testing.T) { - db := offchainreporting.NewTestDB(t, sqlDB, spec.ID) + db := ocr.NewTestDB(t, sqlDB, spec.ID) err := db.WriteConfig(ctx, config) require.NoError(t, err) - db = offchainreporting.NewTestDB(t, sqlDB, -1) + db = ocr.NewTestDB(t, sqlDB, -1) readConfig, err := db.ReadConfig(ctx) require.NoError(t, err) @@ -195,8 +195,8 @@ func Test_DB_PendingTransmissions(t *testing.T) { spec := cltest.MustInsertOffchainreportingOracleSpec(t, db, key.Address) spec2 := cltest.MustInsertOffchainreportingOracleSpec(t, db, key.Address) - odb := offchainreporting.NewTestDB(t, sqlDB, spec.ID) - odb2 := offchainreporting.NewTestDB(t, sqlDB, spec2.ID) + odb := ocr.NewTestDB(t, sqlDB, spec.ID) + odb2 := ocr.NewTestDB(t, sqlDB, spec2.ID) configDigest := cltest.MakeConfigDigest(t) k := ocrtypes.PendingTransmissionKey{ @@ -384,7 +384,7 @@ func Test_DB_PendingTransmissions(t *testing.T) { require.Len(t, m, 1) // Didn't affect other oracleSpecIDs - odb = offchainreporting.NewTestDB(t, sqlDB, spec2.ID) + odb = ocr.NewTestDB(t, sqlDB, spec2.ID) m, err = odb.PendingTransmissionsWithConfigDigest(ctx, configDigest) require.NoError(t, err) require.Len(t, m, 1) @@ -397,8 +397,8 @@ func Test_DB_LatestRoundRequested(t *testing.T) { pgtest.MustExec(t, db, `SET CONSTRAINTS offchainreporting_latest_roun_offchainreporting_oracle_spe_fkey DEFERRED`) - odb := offchainreporting.NewTestDB(t, sqlDB, 1) - odb2 := offchainreporting.NewTestDB(t, sqlDB, 2) + odb := ocr.NewTestDB(t, sqlDB, 1) + odb2 := ocr.NewTestDB(t, sqlDB, 2) rawLog := cltest.LogFromFixture(t, "../../testdata/jsonrpc/round_requested_log_1_1.json") @@ -442,7 +442,7 @@ func Test_DB_LatestRoundRequested(t *testing.T) { }) t.Run("spec with latest round requested can be deleted", func(t *testing.T) { - _, err := sqlDB.Exec(`DELETE FROM offchainreporting_oracle_specs`) + _, err := sqlDB.Exec(`DELETE FROM ocr_oracle_specs`) assert.NoError(t, err) }) } diff --git a/core/services/offchainreporting/delegate.go b/core/services/ocr/delegate.go similarity index 91% rename from core/services/offchainreporting/delegate.go rename to core/services/ocr/delegate.go index a1ca8901470..755ef60a864 100644 --- a/core/services/offchainreporting/delegate.go +++ b/core/services/ocr/delegate.go @@ -1,29 +1,29 @@ -package offchainreporting +package ocr import ( "fmt" "strings" "time" - "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/pkg/errors" "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + ocr "github.com/smartcontractkit/libocr/offchainreporting" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/offchain_aggregator_wrapper" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/telemetry" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - ocr "github.com/smartcontractkit/libocr/offchainreporting" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ) type Delegate struct { @@ -71,15 +71,15 @@ func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} // ServicesForSpec returns the OCR services that need to run for this job -func (d Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err error) { - if jb.OffchainreportingOracleSpec == nil { +func (d Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err error) { + if jb.OCROracleSpec == nil { return nil, errors.Errorf("offchainreporting.Delegate expects an *job.OffchainreportingOracleSpec to be present, got %v", jb) } - chain, err := d.chainSet.Get(jb.OffchainreportingOracleSpec.EVMChainID.ToInt()) + chain, err := d.chainSet.Get(jb.OCROracleSpec.EVMChainID.ToInt()) if err != nil { return nil, err } - concreteSpec, err := job.LoadEnvConfigVarsOCR(chain.Config(), d.keyStore.P2P(), *jb.OffchainreportingOracleSpec) + concreteSpec, err := job.LoadEnvConfigVarsOCR(chain.Config(), d.keyStore.P2P(), *jb.OCROracleSpec) if err != nil { return nil, err } @@ -163,7 +163,8 @@ func (d Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err error if err != nil { return nil, errors.Wrap(err, "error calling NewBootstrapNode") } - services = append(services, bootstrapper) + bootstrapperCtx := job.NewServiceAdapter(bootstrapper) + services = append(services, bootstrapperCtx) } else { if len(bootstrapPeers) < 1 { return nil, errors.New("need at least one bootstrap peer") @@ -178,11 +179,15 @@ func (d Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err error return nil, errors.Wrap(err, "could not get contract ABI JSON") } - strategy := bulletprooftxmanager.NewQueueingTxStrategy(jb.ExternalJobID, chain.Config().OCRDefaultTransactionQueueDepth()) + strategy := txmgr.NewQueueingTxStrategy(jb.ExternalJobID, chain.Config().OCRDefaultTransactionQueueDepth()) - var checker bulletprooftxmanager.TransmitCheckerSpec + var checker txmgr.TransmitCheckerSpec if chain.Config().OCRSimulateTransactions() { - checker.CheckerType = bulletprooftxmanager.TransmitCheckerTypeSimulate + checker.CheckerType = txmgr.TransmitCheckerTypeSimulate + } + + if concreteSpec.TransmitterAddress == nil { + return nil, errors.New("TransmitterAddress is missing") } contractTransmitter := NewOCRContractTransmitter( @@ -241,12 +246,13 @@ func (d Delegate) ServicesForSpec(jb job.Job) (services []job.Service, err error if err != nil { return nil, errors.Wrap(err, "error calling NewOracle") } - services = append(services, oracle) + oracleCtx := job.NewServiceAdapter(oracle) + services = append(services, oracleCtx) // RunResultSaver needs to be started first so its available // to read db writes. It is stopped last after the Oracle is shut down // so no further runs are enqueued and we can drain the queue. - services = append([]job.Service{ocrcommon.NewResultRunSaver( + services = append([]job.ServiceCtx{ocrcommon.NewResultRunSaver( runResults, d.pipelineRunner, make(chan struct{}), diff --git a/core/services/offchainreporting/example-job-spec.toml b/core/services/ocr/example-job-spec.toml similarity index 100% rename from core/services/offchainreporting/example-job-spec.toml rename to core/services/ocr/example-job-spec.toml diff --git a/core/services/offchainreporting/flags.go b/core/services/ocr/flags.go similarity index 98% rename from core/services/offchainreporting/flags.go rename to core/services/ocr/flags.go index 2a0c7c19a4f..7ef9cbf1ff7 100644 --- a/core/services/offchainreporting/flags.go +++ b/core/services/ocr/flags.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "github.com/ethereum/go-ethereum/common" diff --git a/core/services/offchainreporting/flags_test.go b/core/services/ocr/flags_test.go similarity index 97% rename from core/services/offchainreporting/flags_test.go rename to core/services/ocr/flags_test.go index 871a397ccb6..7b183473a88 100644 --- a/core/services/offchainreporting/flags_test.go +++ b/core/services/ocr/flags_test.go @@ -1,4 +1,4 @@ -package offchainreporting_test +package ocr_test import ( "testing" diff --git a/core/services/offchainreporting/helpers_internal_test.go b/core/services/ocr/helpers_internal_test.go similarity index 92% rename from core/services/offchainreporting/helpers_internal_test.go rename to core/services/ocr/helpers_internal_test.go index e41a1216832..aa41c3a52a6 100644 --- a/core/services/offchainreporting/helpers_internal_test.go +++ b/core/services/ocr/helpers_internal_test.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "database/sql" diff --git a/core/services/offchainreporting/mocks/ocr_contract_tracker_db.go b/core/services/ocr/mocks/ocr_contract_tracker_db.go similarity index 99% rename from core/services/offchainreporting/mocks/ocr_contract_tracker_db.go rename to core/services/ocr/mocks/ocr_contract_tracker_db.go index e56ab2809a8..b1175c4fa66 100644 --- a/core/services/offchainreporting/mocks/ocr_contract_tracker_db.go +++ b/core/services/ocr/mocks/ocr_contract_tracker_db.go @@ -3,9 +3,10 @@ package mocks import ( - offchainaggregator "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" mock "github.com/stretchr/testify/mock" + offchainaggregator "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + pg "github.com/smartcontractkit/chainlink/core/services/pg" ) diff --git a/core/services/offchainreporting/validate.go b/core/services/ocr/validate.go similarity index 90% rename from core/services/offchainreporting/validate.go rename to core/services/ocr/validate.go index f7c13c60623..64bca6ee250 100644 --- a/core/services/offchainreporting/validate.go +++ b/core/services/ocr/validate.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "time" @@ -34,7 +34,7 @@ type ValidationConfig interface { // ValidatedOracleSpecToml validates an oracle spec that came from TOML func ValidatedOracleSpecToml(chainSet evm.ChainSet, tomlString string) (job.Job, error) { var jb = job.Job{} - var spec job.OffchainReportingOracleSpec + var spec job.OCROracleSpec tree, err := toml.Load(tomlString) if err != nil { return jb, errors.Wrap(err, "toml error on load") @@ -49,7 +49,7 @@ func ValidatedOracleSpecToml(chainSet evm.ChainSet, tomlString string) (job.Job, if err != nil { return jb, errors.Wrap(err, "toml unmarshal error on job") } - jb.OffchainreportingOracleSpec = &spec + jb.OCROracleSpec = &spec if jb.Type != job.OffchainReporting { return jb, errors.Errorf("the only supported type is currently 'offchainreporting', got %s", jb.Type) @@ -63,7 +63,7 @@ func ValidatedOracleSpecToml(chainSet evm.ChainSet, tomlString string) (job.Job, } } - chain, err := chainSet.Get(jb.OffchainreportingOracleSpec.EVMChainID.ToInt()) + chain, err := chainSet.Get(jb.OCROracleSpec.EVMChainID.ToInt()) if err != nil { return jb, err } @@ -98,7 +98,7 @@ var ( } ) -func validateTimingParameters(cfg ValidationConfig, spec job.OffchainReportingOracleSpec) error { +func validateTimingParameters(cfg ValidationConfig, spec job.OCROracleSpec) error { lc := toLocalConfig(cfg, spec) return errors.Wrap(offchainreporting.SanityCheckLocalConfig(lc), "offchainreporting.SanityCheckLocalConfig failed") } @@ -123,8 +123,8 @@ func validateNonBootstrapSpec(tree *toml.Tree, config ValidationConfig, spec job return errors.New("no pipeline specified") } var observationTimeout time.Duration - if spec.OffchainreportingOracleSpec.ObservationTimeout != 0 { - observationTimeout = spec.OffchainreportingOracleSpec.ObservationTimeout.Duration() + if spec.OCROracleSpec.ObservationTimeout != 0 { + observationTimeout = spec.OCROracleSpec.ObservationTimeout.Duration() } else { observationTimeout = config.OCRObservationTimeout() } diff --git a/core/services/offchainreporting/validate_test.go b/core/services/ocr/validate_test.go similarity index 97% rename from core/services/offchainreporting/validate_test.go rename to core/services/ocr/validate_test.go index 87159526d89..5d5888914de 100644 --- a/core/services/offchainreporting/validate_test.go +++ b/core/services/ocr/validate_test.go @@ -1,4 +1,4 @@ -package offchainreporting +package ocr import ( "testing" @@ -40,9 +40,9 @@ answer1 [type=median index=0]; require.NoError(t, err) // Should be able to jsonapi marshal/unmarshal the minimum spec. // This ensures the UnmarshalJSON's defined on the fields handle a min spec correctly. - b, err := jsonapi.Marshal(os.OffchainreportingOracleSpec) + b, err := jsonapi.Marshal(os.OCROracleSpec) require.NoError(t, err) - var r job.OffchainReportingOracleSpec + var r job.OCROracleSpec err = jsonapi.Unmarshal(b, &r) require.NoError(t, err) }, @@ -64,7 +64,7 @@ transmitterAddress = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" observationTimeout = "10s" databaseTimeout = "2s" observationGracePeriod = "2s" -contractTransmitterTransmitTimeout = "500ms" +contractTransmitterTransmitTimeout = "1s" observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; @@ -76,7 +76,7 @@ answer1 [type=median index=0]; assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) assert.Equal(t, 1, int(os.SchemaVersion)) - assert.False(t, os.OffchainreportingOracleSpec.IsBootstrapPeer) + assert.False(t, os.OCROracleSpec.IsBootstrapPeer) }, }, { @@ -92,7 +92,7 @@ isBootstrapPeer = true assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) assert.Equal(t, 1, int(os.SchemaVersion)) - assert.True(t, os.OffchainreportingOracleSpec.IsBootstrapPeer) + assert.True(t, os.OCROracleSpec.IsBootstrapPeer) }, }, { diff --git a/core/services/offchainreporting2/database.go b/core/services/ocr2/database.go similarity index 84% rename from core/services/offchainreporting2/database.go rename to core/services/ocr2/database.go index 0954f29f147..54320b1ad3c 100644 --- a/core/services/offchainreporting2/database.go +++ b/core/services/ocr2/database.go @@ -1,4 +1,4 @@ -package offchainreporting2 +package ocr2 import ( "context" @@ -6,13 +6,12 @@ import ( "encoding/binary" "time" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/lib/pq" "github.com/pkg/errors" - ocrcommon "github.com/smartcontractkit/libocr/commontypes" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink/core/logger" ) type db struct { @@ -33,8 +32,8 @@ func NewDB(sqldb *sql.DB, oracleSpecID int32, lggr logger.Logger) *db { func (d *db) ReadState(ctx context.Context, cd ocrtypes.ConfigDigest) (ps *ocrtypes.PersistentState, err error) { q := d.QueryRowContext(ctx, ` SELECT epoch, highest_sent_epoch, highest_received_epoch -FROM offchainreporting2_persistent_states -WHERE offchainreporting2_oracle_spec_id = $1 AND config_digest = $2 +FROM ocr2_persistent_states +WHERE ocr2_oracle_spec_id = $1 AND config_digest = $2 LIMIT 1`, d.oracleSpecID, cd) ps = new(ocrtypes.PersistentState) @@ -61,8 +60,8 @@ func (d *db) WriteState(ctx context.Context, cd ocrtypes.ConfigDigest, state ocr highestReceivedEpoch = append(highestReceivedEpoch, int64(v)) } _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting2_persistent_states ( - offchainreporting2_oracle_spec_id, +INSERT INTO ocr2_persistent_states ( + ocr2_oracle_spec_id, config_digest, epoch, highest_sent_epoch, @@ -71,7 +70,7 @@ INSERT INTO offchainreporting2_persistent_states ( updated_at ) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) -ON CONFLICT (offchainreporting2_oracle_spec_id, config_digest) +ON CONFLICT (ocr2_oracle_spec_id, config_digest) DO UPDATE SET ( epoch, highest_sent_epoch, @@ -98,8 +97,8 @@ SELECT onchain_config, offchain_config_version, offchain_config -FROM offchainreporting2_contract_configs -WHERE offchainreporting2_oracle_spec_id = $1 +FROM ocr2_contract_configs +WHERE ocr2_oracle_spec_id = $1 LIMIT 1`, d.oracleSpecID) c = new(ocrtypes.ContractConfig) @@ -146,8 +145,8 @@ func (d *db) WriteConfig(ctx context.Context, c ocrtypes.ContractConfig) error { signers = append(signers, []byte(s)) } _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting2_contract_configs ( - offchainreporting2_oracle_spec_id, +INSERT INTO ocr2_contract_configs ( + ocr2_oracle_spec_id, config_digest, config_count, signers, @@ -160,7 +159,7 @@ INSERT INTO offchainreporting2_contract_configs ( updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) -ON CONFLICT (offchainreporting2_oracle_spec_id) DO UPDATE SET +ON CONFLICT (ocr2_oracle_spec_id) DO UPDATE SET config_digest = EXCLUDED.config_digest, config_count = EXCLUDED.config_count, signers = EXCLUDED.signers, @@ -201,8 +200,8 @@ func (d *db) StorePendingTransmission(ctx context.Context, t ocrtypes.ReportTime copy(extraHash[:], tx.ExtraHash[:]) _, err := d.ExecContext(ctx, ` -INSERT INTO offchainreporting2_pending_transmissions ( - offchainreporting2_oracle_spec_id, +INSERT INTO ocr2_pending_transmissions ( + ocr2_oracle_spec_id, config_digest, epoch, round, @@ -216,8 +215,8 @@ INSERT INTO offchainreporting2_pending_transmissions ( updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW()) -ON CONFLICT (offchainreporting2_oracle_spec_id, config_digest, epoch, round) DO UPDATE SET - offchainreporting2_oracle_spec_id = EXCLUDED.offchainreporting2_oracle_spec_id, +ON CONFLICT (ocr2_oracle_spec_id, config_digest, epoch, round) DO UPDATE SET + ocr2_oracle_spec_id = EXCLUDED.ocr2_oracle_spec_id, config_digest = EXCLUDED.config_digest, epoch = EXCLUDED.epoch, round = EXCLUDED.round, @@ -252,13 +251,13 @@ SELECT extra_hash, report, attributed_signatures -FROM offchainreporting2_pending_transmissions -WHERE offchainreporting2_oracle_spec_id = $1 AND config_digest = $2 +FROM ocr2_pending_transmissions +WHERE ocr2_oracle_spec_id = $1 AND config_digest = $2 `, d.oracleSpecID, cd) if err != nil { return nil, errors.Wrap(err, "PendingTransmissionsWithConfigDigest failed to query rows") } - defer d.lggr.ErrorIfClosing(rows, "offchainreporting2_pending_transmissions rows") + defer d.lggr.ErrorIfClosing(rows, "ocr2_pending_transmissions rows") m := make(map[ocrtypes.ReportTimestamp]ocrtypes.PendingTransmission) @@ -301,8 +300,8 @@ WHERE offchainreporting2_oracle_spec_id = $1 AND config_digest = $2 func (d *db) DeletePendingTransmission(ctx context.Context, t ocrtypes.ReportTimestamp) (err error) { _, err = d.ExecContext(ctx, ` -DELETE FROM offchainreporting2_pending_transmissions -WHERE offchainreporting2_oracle_spec_id = $1 AND config_digest = $2 AND epoch = $3 AND round = $4 +DELETE FROM ocr2_pending_transmissions +WHERE ocr2_oracle_spec_id = $1 AND config_digest = $2 AND epoch = $3 AND round = $4 `, d.oracleSpecID, t.ConfigDigest, t.Epoch, t.Round) err = errors.Wrap(err, "DeletePendingTransmission failed") @@ -312,8 +311,8 @@ WHERE offchainreporting2_oracle_spec_id = $1 AND config_digest = $2 AND epoch = func (d *db) DeletePendingTransmissionsOlderThan(ctx context.Context, t time.Time) (err error) { _, err = d.ExecContext(ctx, ` -DELETE FROM offchainreporting2_pending_transmissions -WHERE offchainreporting2_oracle_spec_id = $1 AND time < $2 +DELETE FROM ocr2_pending_transmissions +WHERE ocr2_oracle_spec_id = $1 AND time < $2 `, d.oracleSpecID, t) err = errors.Wrap(err, "DeletePendingTransmissionsOlderThan failed") diff --git a/core/services/offchainreporting2/database_test.go b/core/services/ocr2/database_test.go similarity index 86% rename from core/services/offchainreporting2/database_test.go rename to core/services/ocr2/database_test.go index daee3ecd92c..855f9dcdad7 100644 --- a/core/services/offchainreporting2/database_test.go +++ b/core/services/ocr2/database_test.go @@ -1,41 +1,49 @@ -package offchainreporting2_test +package ocr2_test import ( "context" + "encoding/json" "testing" "time" - "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2/testhelpers" - - "github.com/smartcontractkit/chainlink/core/internal/testutils" - "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" + medianconfig "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins/median/config" - "github.com/smartcontractkit/chainlink/core/logger" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/sqlx" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" - offchainreporting "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" + "github.com/smartcontractkit/chainlink/core/services/ocr2" + "github.com/smartcontractkit/chainlink/core/services/ocr2/testhelpers" ) var ctx = context.Background() -func MustInsertOffchainreportingOracleSpec(t *testing.T, db *sqlx.DB, transmitterAddress ethkey.EIP55Address) job.OffchainReporting2OracleSpec { +func MustInsertOCROracleSpec(t *testing.T, db *sqlx.DB, transmitterAddress ethkey.EIP55Address) job.OCR2OracleSpec { t.Helper() - spec := job.OffchainReporting2OracleSpec{} + spec := job.OCR2OracleSpec{} mockJuelsPerFeeCoinSource := `ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0];` - require.NoError(t, db.Get(&spec, `INSERT INTO offchainreporting2_oracle_specs (created_at, updated_at, relay, relay_config, contract_id, p2p_bootstrap_peers, ocr_key_bundle_id, monitoring_endpoint, transmitter_id, blockchain_timeout, contract_config_tracker_poll_interval, contract_config_confirmations, juels_per_fee_coin_pipeline) VALUES ( -NOW(),NOW(), 'ethereum', '{}', $1,'{}',$2,$3,$4,0,0,0,$5 -) RETURNING *`, cltest.NewEIP55Address().String(), cltest.DefaultOCR2KeyBundleID, "chain.link:1234", transmitterAddress.String(), mockJuelsPerFeeCoinSource)) + config := medianconfig.PluginConfig{JuelsPerFeeCoinPipeline: mockJuelsPerFeeCoinSource} + jsonConfig, err := json.Marshal(config) + require.NoError(t, err) + + require.NoError(t, db.Get(&spec, `INSERT INTO ocr2_oracle_specs ( +relay, relay_config, contract_id, p2p_bootstrap_peers, ocr_key_bundle_id, monitoring_endpoint, transmitter_id, +blockchain_timeout, contract_config_tracker_poll_interval, contract_config_confirmations, plugin_type, plugin_config, created_at, updated_at) VALUES ( +'ethereum', '{}', $1, '{}', $2, $3, $4, +0, 0, 0, 'median', $5, NOW(), NOW() +) RETURNING *`, cltest.NewEIP55Address().String(), cltest.DefaultOCR2KeyBundleID, "chain.link:1234", transmitterAddress.String(), jsonConfig)) return spec } @@ -54,11 +62,11 @@ func Test_DB_ReadWriteState(t *testing.T) { cfg := configtest.NewTestGeneralConfig(t) ethKeyStore := cltest.NewKeyStore(t, sqlDB, cfg).Eth() key, _ := cltest.MustInsertRandomKey(t, ethKeyStore) - spec := MustInsertOffchainreportingOracleSpec(t, sqlDB, key.Address) + spec := MustInsertOCROracleSpec(t, sqlDB, key.Address) lggr := logger.TestLogger(t) t.Run("reads and writes state", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) state := ocrtypes.PersistentState{ Epoch: 1, HighestSentEpoch: 2, @@ -75,7 +83,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("updates state", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) newState := ocrtypes.PersistentState{ Epoch: 2, HighestSentEpoch: 3, @@ -92,7 +100,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("does not return result for wrong spec", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) state := ocrtypes.PersistentState{ Epoch: 3, HighestSentEpoch: 4, @@ -103,7 +111,7 @@ func Test_DB_ReadWriteState(t *testing.T) { require.NoError(t, err) // odb with different spec - db = offchainreporting.NewDB(sqlDB.DB, -1, lggr) + db = ocr2.NewDB(sqlDB.DB, -1, lggr) readState, err := db.ReadState(ctx, configDigest) require.NoError(t, err) @@ -112,7 +120,7 @@ func Test_DB_ReadWriteState(t *testing.T) { }) t.Run("does not return result for wrong config digest", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) state := ocrtypes.PersistentState{ Epoch: 4, HighestSentEpoch: 5, @@ -145,11 +153,11 @@ func Test_DB_ReadWriteConfig(t *testing.T) { cfg := configtest.NewTestGeneralConfig(t) ethKeyStore := cltest.NewKeyStore(t, sqlDB, cfg).Eth() key, _ := cltest.MustInsertRandomKey(t, ethKeyStore) - spec := MustInsertOffchainreportingOracleSpec(t, sqlDB, key.Address) + spec := MustInsertOCROracleSpec(t, sqlDB, key.Address) lggr := logger.TestLogger(t) t.Run("reads and writes config", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) err := db.WriteConfig(ctx, config) require.NoError(t, err) @@ -161,7 +169,7 @@ func Test_DB_ReadWriteConfig(t *testing.T) { }) t.Run("updates config", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) newConfig := ocrtypes.ContractConfig{ ConfigDigest: testhelpers.MakeConfigDigest(t), @@ -179,12 +187,12 @@ func Test_DB_ReadWriteConfig(t *testing.T) { }) t.Run("does not return result for wrong spec", func(t *testing.T) { - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) err := db.WriteConfig(ctx, config) require.NoError(t, err) - db = offchainreporting.NewDB(sqlDB.DB, -1, lggr) + db = ocr2.NewDB(sqlDB.DB, -1, lggr) readConfig, err := db.ReadConfig(ctx) require.NoError(t, err) @@ -210,10 +218,10 @@ func Test_DB_PendingTransmissions(t *testing.T) { key, _ := cltest.MustInsertRandomKey(t, ethKeyStore) lggr := logger.TestLogger(t) - spec := MustInsertOffchainreportingOracleSpec(t, sqlDB, key.Address) - spec2 := MustInsertOffchainreportingOracleSpec(t, sqlDB, key.Address) - db := offchainreporting.NewDB(sqlDB.DB, spec.ID, lggr) - db2 := offchainreporting.NewDB(sqlDB.DB, spec2.ID, lggr) + spec := MustInsertOCROracleSpec(t, sqlDB, key.Address) + spec2 := MustInsertOCROracleSpec(t, sqlDB, key.Address) + db := ocr2.NewDB(sqlDB.DB, spec.ID, lggr) + db2 := ocr2.NewDB(sqlDB.DB, spec2.ID, lggr) configDigest := testhelpers.MakeConfigDigest(t) k := ocrtypes.ReportTimestamp{ @@ -403,7 +411,7 @@ func Test_DB_PendingTransmissions(t *testing.T) { require.Len(t, m, 1) // Didn't affect other oracleSpecIDs - db = offchainreporting.NewDB(sqlDB.DB, spec2.ID, lggr) + db = ocr2.NewDB(sqlDB.DB, spec2.ID, lggr) m, err = db.PendingTransmissionsWithConfigDigest(ctx, configDigest) require.NoError(t, err) require.Len(t, m, 1) diff --git a/core/services/offchainreporting2/delegate.go b/core/services/ocr2/delegate.go similarity index 71% rename from core/services/offchainreporting2/delegate.go rename to core/services/ocr2/delegate.go index d998db4c6a9..5b33d9e144f 100644 --- a/core/services/offchainreporting2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -1,17 +1,17 @@ -package offchainreporting2 +package ocr2 import ( - "time" - "github.com/pkg/errors" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2" - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/core/chains/evm" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" + "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins" + "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins/median" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/relay" @@ -26,10 +26,10 @@ type Delegate struct { peerWrapper *ocrcommon.SingletonPeerWrapper monitoringEndpointGen telemetry.MonitoringEndpointGenerator chainSet evm.ChainSet - cfg Config + cfg validate.Config lggr logger.Logger ks keystore.OCR2 - relayer types.Relayer + relayer types.RelayerCtx } var _ job.Delegate = (*Delegate)(nil) @@ -42,9 +42,9 @@ func NewDelegate( monitoringEndpointGen telemetry.MonitoringEndpointGenerator, chainSet evm.ChainSet, lggr logger.Logger, - cfg Config, + cfg validate.Config, ks keystore.OCR2, - relayer types.Relayer, + relayer types.RelayerCtx, ) *Delegate { return &Delegate{ db, @@ -70,10 +70,11 @@ func (Delegate) OnJobDeleted(spec job.Job) {} func (Delegate) AfterJobCreated(spec job.Job) {} func (Delegate) BeforeJobDeleted(spec job.Job) {} -func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err error) { - spec := jobSpec.Offchainreporting2OracleSpec +// ServicesForSpec returns the OCR2 services that need to run for this job +func (d Delegate) ServicesForSpec(jobSpec job.Job) ([]job.ServiceCtx, error) { + spec := jobSpec.OCR2OracleSpec if spec == nil { - return nil, errors.Errorf("offchainreporting.Delegate expects an *job.Offchainreporting2OracleSpec to be present, got %v", jobSpec) + return nil, errors.Errorf("offchainreporting2.Delegate expects an *job.Offchainreporting2OracleSpec to be present, got %v", jobSpec) } ocr2Provider, err := d.relayer.NewOCR2Provider(jobSpec.ExternalJobID, &relay.OCR2ProviderArgs{ @@ -82,12 +83,12 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err TransmitterID: spec.TransmitterID, Relay: spec.Relay, RelayConfig: spec.RelayConfig, + Plugin: spec.PluginType, IsBootstrapPeer: false, }) if err != nil { return nil, errors.Wrap(err, "error calling 'relayer.NewOCR2Provider'") } - services = append(services, ocr2Provider) ocrDB := NewDB(d.db.DB, spec.ID, d.lggr) peerWrapper := d.peerWrapper @@ -97,17 +98,16 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err return nil, errors.New("peerWrapper is not started. OCR2 jobs require a started and running peer. Did you forget to specify P2P_LISTEN_PORT?") } - loggerWith := d.lggr.With( - "OCRLogger", "true", + lggr := d.lggr.Named("OCR").With( "contractID", spec.ContractID, "jobName", jobSpec.Name.ValueOrZero(), "jobID", jobSpec.ID, ) - ocrLogger := logger.NewOCRWrapper(loggerWith, true, func(msg string) { + ocrLogger := logger.NewOCRWrapper(lggr, true, func(msg string) { d.lggr.ErrorIf(d.jobORM.RecordError(jobSpec.ID, msg), "unable to record error") }) - lc := ToLocalConfig(d.cfg, *spec) + lc := validate.ToLocalConfig(d.cfg, *spec) if err = libocr2.SanityCheckLocalConfig(lc); err != nil { return nil, err } @@ -138,7 +138,6 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err if err != nil { return nil, err } - runResults := make(chan pipeline.Run, d.cfg.JobPipelineResultWriteQueueDepth()) // These are populated here because when the pipeline spec is @@ -147,22 +146,23 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err jobSpec.PipelineSpec.JobName = jobSpec.Name.ValueOrZero() jobSpec.PipelineSpec.JobID = jobSpec.ID - juelsPerFeeCoinPipelineSpec := pipeline.Spec{ - ID: jobSpec.ID, - DotDagSource: spec.JuelsPerFeeCoinPipeline, - CreatedAt: time.Now(), + var pluginOracle plugins.OraclePlugin + switch spec.PluginType { + case job.Median: + pluginOracle, err = median.NewMedian(jobSpec, ocr2Provider, d.pipelineRunner, runResults, lggr, ocrLogger) + default: + return nil, errors.Errorf("plugin type %s not supported", spec.PluginType) + } + if err != nil { + return nil, errors.Wrap(err, "failed to initialise plugin") } - numericalMedianFactory := median.NumericalMedianFactory{ - ContractTransmitter: ocr2Provider.MedianContract(), - DataSource: ocrcommon.NewDataSourceV2(d.pipelineRunner, - jobSpec, - *jobSpec.PipelineSpec, - loggerWith, - runResults, - ), - JuelsPerFeeCoinDataSource: ocrcommon.NewInMemoryDataSource(d.pipelineRunner, jobSpec, juelsPerFeeCoinPipelineSpec, loggerWith), - ReportCodec: ocr2Provider.ReportCodec(), - Logger: ocrLogger, + pluginFactory, err := pluginOracle.GetPluginFactory() + if err != nil { + return nil, errors.Wrap(err, "failed to get plugin factory") + } + pluginServices, err := pluginOracle.GetServices() + if err != nil { + return nil, errors.Wrap(err, "failed to get plugin services") } oracle, err := libocr2.NewOracle(libocr2.OracleArgs{ @@ -177,22 +177,21 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err OffchainConfigDigester: offchainConfigDigester, OffchainKeyring: kb, OnchainKeyring: kb, - ReportingPluginFactory: numericalMedianFactory, + ReportingPluginFactory: pluginFactory, }) if err != nil { return nil, errors.Wrap(err, "error calling NewOracle") } - services = append(services, oracle) - // RunResultSaver needs to be started first so its available - // to read odb writes. It is stopped last after the Oracle is shut down - // so no further runs are enqueued and we can drain the queue. - services = append([]job.Service{ocrcommon.NewResultRunSaver( + // RunResultSaver needs to be started first, so it's available + // to read odb writes. It is stopped last after the OraclePlugin is shut down + // so no further runs are enqueued, and we can drain the queue. + runResultSaver := ocrcommon.NewResultRunSaver( runResults, d.pipelineRunner, make(chan struct{}), - loggerWith, - )}, services...) + lggr) - return services, nil + oracleCtx := job.NewServiceAdapter(oracle) + return append([]job.ServiceCtx{runResultSaver, ocr2Provider, oracleCtx}, pluginServices...), nil } diff --git a/core/services/ocr2/mocks/config.go b/core/services/ocr2/mocks/config.go new file mode 100644 index 00000000000..59ac35dfeca --- /dev/null +++ b/core/services/ocr2/mocks/config.go @@ -0,0 +1,175 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// Config is an autogenerated mock type for the Config type +type Config struct { + mock.Mock +} + +// Dev provides a mock function with given fields: +func (_m *Config) Dev() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// JobPipelineResultWriteQueueDepth provides a mock function with given fields: +func (_m *Config) JobPipelineResultWriteQueueDepth() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +// OCR2BlockchainTimeout provides a mock function with given fields: +func (_m *Config) OCR2BlockchainTimeout() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// OCR2ContractConfirmations provides a mock function with given fields: +func (_m *Config) OCR2ContractConfirmations() uint16 { + ret := _m.Called() + + var r0 uint16 + if rf, ok := ret.Get(0).(func() uint16); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint16) + } + + return r0 +} + +// OCR2ContractPollInterval provides a mock function with given fields: +func (_m *Config) OCR2ContractPollInterval() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// OCR2ContractSubscribeInterval provides a mock function with given fields: +func (_m *Config) OCR2ContractSubscribeInterval() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// OCR2ContractTransmitterTransmitTimeout provides a mock function with given fields: +func (_m *Config) OCR2ContractTransmitterTransmitTimeout() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// OCR2DatabaseTimeout provides a mock function with given fields: +func (_m *Config) OCR2DatabaseTimeout() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// OCR2KeyBundleID provides a mock function with given fields: +func (_m *Config) OCR2KeyBundleID() (string, error) { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OCR2MonitoringEndpoint provides a mock function with given fields: +func (_m *Config) OCR2MonitoringEndpoint() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// OCR2TraceLogging provides a mock function with given fields: +func (_m *Config) OCR2TraceLogging() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} diff --git a/core/services/offchainreporting2/mocks/ocr_contract_tracker_db.go b/core/services/ocr2/mocks/ocr_contract_tracker_db.go similarity index 100% rename from core/services/offchainreporting2/mocks/ocr_contract_tracker_db.go rename to core/services/ocr2/mocks/ocr_contract_tracker_db.go diff --git a/core/services/ocr2/plugins/median/config/config.go b/core/services/ocr2/plugins/median/config/config.go new file mode 100644 index 00000000000..27c69773ed0 --- /dev/null +++ b/core/services/ocr2/plugins/median/config/config.go @@ -0,0 +1,24 @@ +// config is a separate package so that we can validate +// the config in other packages, for example in job at job create time. + +package config + +import ( + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink/core/services/pipeline" +) + +// The PluginConfig struct contains the custom arguments needed for the Median plugin. +type PluginConfig struct { + JuelsPerFeeCoinPipeline string `json:"juelsPerFeeCoinSource"` +} + +// ValidatePluginConfig validates the arguments for the Median plugin. +func ValidatePluginConfig(config PluginConfig) error { + if _, err := pipeline.Parse(config.JuelsPerFeeCoinPipeline); err != nil { + return errors.Wrap(err, "invalid juelsPerFeeCoinSource pipeline") + } + + return nil +} diff --git a/core/services/ocr2/plugins/median/plugin.go b/core/services/ocr2/plugins/median/plugin.go new file mode 100644 index 00000000000..97f1382a77b --- /dev/null +++ b/core/services/ocr2/plugins/median/plugin.go @@ -0,0 +1,83 @@ +package median + +import ( + "encoding/json" + "time" + + "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins/median/config" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/core/services/pipeline" + "github.com/smartcontractkit/chainlink/core/services/relay/types" +) + +// The Median struct holds parameters needed to run a Median plugin. +type Median struct { + jb job.Job + ocr2Provider types.OCR2ProviderCtx + pipelineRunner pipeline.Runner + runResults chan pipeline.Run + lggr logger.Logger + ocrLogger commontypes.Logger + + pluginConfig config.PluginConfig +} + +var _ plugins.OraclePlugin = &Median{} + +// NewMedian parses the arguments and returns a new Median struct. +func NewMedian(jb job.Job, ocr2Provider types.OCR2ProviderCtx, pipelineRunner pipeline.Runner, runResults chan pipeline.Run, lggr logger.Logger, ocrLogger commontypes.Logger) (*Median, error) { + var pluginConfig config.PluginConfig + err := json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &pluginConfig) + if err != nil { + return &Median{}, err + } + err = config.ValidatePluginConfig(pluginConfig) + if err != nil { + return &Median{}, err + } + + return &Median{ + jb: jb, + ocr2Provider: ocr2Provider, + pipelineRunner: pipelineRunner, + runResults: runResults, + lggr: lggr, + ocrLogger: ocrLogger, + pluginConfig: pluginConfig, + }, nil +} + +// GetPluginFactory return a median.NumericalMedianFactory. +func (m *Median) GetPluginFactory() (ocr2types.ReportingPluginFactory, error) { + juelsPerFeeCoinPipelineSpec := pipeline.Spec{ + ID: m.jb.ID, + DotDagSource: m.pluginConfig.JuelsPerFeeCoinPipeline, + CreatedAt: time.Now(), + } + return median.NumericalMedianFactory{ + ContractTransmitter: m.ocr2Provider.MedianContract(), + DataSource: ocrcommon.NewDataSourceV2(m.pipelineRunner, + m.jb, + *m.jb.PipelineSpec, + m.lggr, + m.runResults, + ), + JuelsPerFeeCoinDataSource: ocrcommon.NewInMemoryDataSource(m.pipelineRunner, m.jb, juelsPerFeeCoinPipelineSpec, m.lggr), + ReportCodec: m.ocr2Provider.ReportCodec(), + Logger: m.ocrLogger, + }, nil +} + +// GetServices return an empty Service slice because Median does not need any services besides the generic OCR2 ones +// supplied in the OCR2 delegate. This method exists to satisfy the plugins.OraclePlugin interface. +func (m *Median) GetServices() ([]job.ServiceCtx, error) { + return []job.ServiceCtx{}, nil +} diff --git a/core/services/ocr2/plugins/plugin.go b/core/services/ocr2/plugins/plugin.go new file mode 100644 index 00000000000..45a78b4ea6b --- /dev/null +++ b/core/services/ocr2/plugins/plugin.go @@ -0,0 +1,17 @@ +package plugins + +import ( + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink/core/services/job" +) + +// OraclePlugin is the interface that every OCR2 plugin needs to implement to be able to run from the generic +// OCR2.Delegate ServicesForSpec method. +type OraclePlugin interface { + // GetPluginFactory return the ocr2types.ReportingPluginFactory object for the given Plugin. + GetPluginFactory() (plugin ocr2types.ReportingPluginFactory, err error) + // GetServices returns any additional services that the plugin might need. This can return an empty slice when + // there are no additional services needed. + GetServices() (services []job.ServiceCtx, err error) +} diff --git a/core/services/offchainreporting2/testhelpers/digest.go b/core/services/ocr2/testhelpers/digest.go similarity index 100% rename from core/services/offchainreporting2/testhelpers/digest.go rename to core/services/ocr2/testhelpers/digest.go diff --git a/core/services/offchainreporting2/config.go b/core/services/ocr2/validate/config.go similarity index 89% rename from core/services/offchainreporting2/config.go rename to core/services/ocr2/validate/config.go index cb959608224..aec7dd2650e 100644 --- a/core/services/offchainreporting2/config.go +++ b/core/services/ocr2/validate/config.go @@ -1,13 +1,16 @@ -package offchainreporting2 +package validate import ( "time" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/libocr/offchainreporting2/types" ) +//go:generate mockery --name Config --output ../mocks/ --case=underscore + // Config contains OCR2 configurations for a job. type Config interface { config.OCR2Config @@ -16,7 +19,7 @@ type Config interface { } // ToLocalConfig creates a OCR2 LocalConfig from the global config and the OCR2 spec. -func ToLocalConfig(config Config, spec job.OffchainReporting2OracleSpec) types.LocalConfig { +func ToLocalConfig(config Config, spec job.OCR2OracleSpec) types.LocalConfig { var ( blockchainTimeout = time.Duration(spec.BlockchainTimeout) ccConfirmations = spec.ContractConfigConfirmations diff --git a/core/services/offchainreporting2/validate.go b/core/services/ocr2/validate/validate.go similarity index 67% rename from core/services/offchainreporting2/validate.go rename to core/services/ocr2/validate/validate.go index 74fe17c3a2f..b393800eddc 100644 --- a/core/services/offchainreporting2/validate.go +++ b/core/services/ocr2/validate/validate.go @@ -1,21 +1,20 @@ -package offchainreporting2 +package validate import ( "github.com/lib/pq" "github.com/pelletier/go-toml" "github.com/pkg/errors" + libocr2 "github.com/smartcontractkit/libocr/offchainreporting2" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/relay" - libocr2 "github.com/smartcontractkit/libocr/offchainreporting2" ) // ValidatedOracleSpecToml validates an oracle spec that came from TOML func ValidatedOracleSpecToml(config Config, tomlString string) (job.Job, error) { var jb = job.Job{} - var spec job.OffchainReporting2OracleSpec + var spec job.OCR2OracleSpec tree, err := toml.Load(tomlString) if err != nil { return jb, errors.Wrap(err, "toml error on load") @@ -30,10 +29,10 @@ func ValidatedOracleSpecToml(config Config, tomlString string) (job.Job, error) if err != nil { return jb, errors.Wrap(err, "toml unmarshal error on job") } - jb.Offchainreporting2OracleSpec = &spec - if jb.Offchainreporting2OracleSpec.P2PBootstrapPeers == nil { + jb.OCR2OracleSpec = &spec + if jb.OCR2OracleSpec.P2PBootstrapPeers == nil { // Empty but non-null, field is non-nullable. - jb.Offchainreporting2OracleSpec.P2PBootstrapPeers = pq.StringArray{} + jb.OCR2OracleSpec.P2PBootstrapPeers = pq.StringArray{} } if jb.Type != job.OffchainReporting2 { @@ -60,22 +59,22 @@ func ValidatedOracleSpecToml(config Config, tomlString string) (job.Job, error) // Parameters that must be explicitly set by the operator. var ( - // Common to both bootstrap and non-boostrap params = map[string]struct{}{ - "type": {}, - "schemaVersion": {}, - "contractID": {}, - "relay": {}, - "relayConfig": {}, - "observationSource": {}, - "juelsPerFeeCoinSource": {}, + "type": {}, + "schemaVersion": {}, + "contractID": {}, + "relay": {}, + "relayConfig": {}, + "pluginType": {}, + "pluginConfig": {}, } notExpectedParams = map[string]struct{}{ - "isBootstrapPeer": {}, + "isBootstrapPeer": {}, + "juelsPerFeeCoinSource": {}, } ) -func validateTimingParameters(config Config, spec job.OffchainReporting2OracleSpec) error { +func validateTimingParameters(config Config, spec job.OCR2OracleSpec) error { lc := ToLocalConfig(config, spec) return libocr2.SanityCheckLocalConfig(lc) } @@ -85,12 +84,16 @@ func validateSpec(tree *toml.Tree, spec job.Job) error { if err := ocrcommon.ValidateExplicitlySetKeys(tree, expected, notExpected, "ocr2"); err != nil { return err } - if spec.Pipeline.Source == "" { - return errors.New("no pipeline specified") - } - // validate that the JuelsPerFeeCoinPipeline is valid (not checked later because it's not a normal pipeline) - if _, err := pipeline.Parse(spec.Offchainreporting2OracleSpec.JuelsPerFeeCoinPipeline); err != nil { - return errors.Wrap(err, "invalid juelsPerFeeCoinSource pipeline") + + switch spec.OCR2OracleSpec.PluginType { + case job.Median: + if spec.Pipeline.Source == "" { + return errors.New("no pipeline specified") + } + case "": + return errors.New("no plugin specified") + default: + return errors.Errorf("invalid pluginType %s", spec.OCR2OracleSpec.PluginType) } return nil diff --git a/core/services/offchainreporting2/validate_test.go b/core/services/ocr2/validate/validate_test.go similarity index 72% rename from core/services/offchainreporting2/validate_test.go rename to core/services/ocr2/validate/validate_test.go index 9635dceb51d..d1556b52ed5 100644 --- a/core/services/offchainreporting2/validate_test.go +++ b/core/services/ocr2/validate/validate_test.go @@ -1,18 +1,21 @@ -package offchainreporting2 +package validate import ( + "encoding/json" "fmt" "testing" "time" "github.com/ethereum/go-ethereum/common/hexutil" + medianconfig "github.com/smartcontractkit/chainlink/core/services/ocr2/plugins/median/config" "github.com/stretchr/testify/assert" "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/core/services/job" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/core/services/job" ) func TestValidateOracleSpec(t *testing.T) { @@ -23,19 +26,23 @@ func TestValidateOracleSpec(t *testing.T) { assertion func(t *testing.T, os job.Job, err error) }{ { - name: "minimal non-bootstrap oracle spec", + name: "minimal OCR2 oracle spec", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" -observationSource = """ +observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ +[relayConfig] +chainID = 1337 +[pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; @@ -43,42 +50,48 @@ ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ -[relayConfig] -chainID = 1337 `, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) // Should be able to jsonapi marshal/unmarshal the minimum spec. // This ensures the UnmarshalJSON's defined on the fields handle a min spec correctly. - b, err := jsonapi.Marshal(os.Offchainreporting2OracleSpec) + b, err := jsonapi.Marshal(os.OCR2OracleSpec) require.NoError(t, err) - var r job.OffchainReporting2OracleSpec + var r job.OCR2OracleSpec err = jsonapi.Unmarshal(b, &r) require.NoError(t, err) + assert.Equal(t, "median", string(r.PluginType)) + var pc medianconfig.PluginConfig + require.NoError(t, json.Unmarshal(r.PluginConfig.Bytes(), &pc)) + require.NoError(t, medianconfig.ValidatePluginConfig(pc)) }, }, { name: "decodes valid oracle spec toml", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [ "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001", ] -ocrKeyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" +ocrKeyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" monitoringEndpoint = "chain.link:4321" transmitterID = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" observationTimeout = "10s" -observationSource = """ +observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ +[relayConfig] +chainID = 1337 +[pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; @@ -86,25 +99,58 @@ ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ -[relayConfig] -chainID = 1337 `, assertion: func(t *testing.T, os job.Job, err error) { require.NoError(t, err) assert.Equal(t, 1, int(os.SchemaVersion)) }, }, + { + name: "raises error on extra keys", + toml: ` +type = "offchainreporting2" +pluginType = "median" +schemaVersion = 1 +relay = "evm" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" +p2pBootstrapPeers = [ +"12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001", +] +isBootstrapPeer = true +ocrKeyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" +monitoringEndpoint = "chain.link:4321" +transmitterID = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" +observationTimeout = "10s" +observationSource = """ +ds1 [type=bridge name=voter_turnout]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +answer1 [type=median index=0]; +""" +[relayConfig] +chainID = 1337 +[pluginConfig] +`, + assertion: func(t *testing.T, os job.Job, err error) { + require.Error(t, err) + assert.Contains(t, err.Error(), "unrecognised key for ocr2 peer: isBootstrapPeer") + }, + }, { name: "empty pipeline string", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [] [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -114,9 +160,10 @@ chainID = 1337 name: "invalid dot", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [] observationSource = """ @@ -124,6 +171,7 @@ observationSource = """ """ [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -133,9 +181,10 @@ chainID = 1337 name: "invalid peer address", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = ["/invalid/peer/address"] observationSource = """ @@ -143,6 +192,7 @@ blah """ [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -152,9 +202,10 @@ chainID = 1337 name: "non-zero timeouts", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = ["12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001"] blockchainTimeout = "0s" @@ -163,6 +214,7 @@ blah """ [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -172,9 +224,10 @@ chainID = 1337 name: "non-zero intervals", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = ["12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001"] observationSource = """ @@ -182,6 +235,7 @@ blah """ [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -191,14 +245,16 @@ chainID = 1337 name: "broken monitoring endpoint", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [] monitoringEndpoint = "\t/fd\2ff )(*&^%$#@" [relayConfig] chainID = 1337 +[pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) @@ -216,24 +272,25 @@ chainID = 1337 name: "invalid global default", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 maxTaskDuration = "30m" relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [ "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001", ] -ocrKeyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" monitoringEndpoint = "chain.link:4321" transmitterID = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" -observationSource = """ +observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ +[pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; @@ -254,19 +311,21 @@ chainID = 1337 }, }, { - name: "invalid juelsPerFeeCoinSource", + name: "invalid pluginType", toml: ` type = "offchainreporting2" +pluginType = "medion" schemaVersion = 1 relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" -observationSource = """ +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ +[pluginConfig] juelsPerFeeCoinSource = """ -> """ @@ -275,23 +334,25 @@ chainID = 1337 `, assertion: func(t *testing.T, os job.Job, err error) { require.Error(t, err) - require.Contains(t, err.Error(), "invalid juelsPerFeeCoinSource pipeline") + require.Contains(t, err.Error(), "invalid pluginType medion") }, }, { name: "invalid relay", toml: ` type = "offchainreporting2" +pluginType = "median" schemaVersion = 1 relay = "blerg" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" -observationSource = """ +contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" +observationSource = """ ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; ds1_multiply [type=multiply times=1.23]; ds1 -> ds1_parse -> ds1_multiply -> answer1; answer1 [type=median index=0]; """ +[pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; """ @@ -299,7 +360,7 @@ ds1 [type=bridge name=voter_turnout]; chainID = 1337 `, assertion: func(t *testing.T, os job.Job, err error) { - fmt.Println("relay", os.Offchainreporting2OracleSpec.Relay) + fmt.Println("relay", os.OCR2OracleSpec.Relay) require.Error(t, err) require.Contains(t, err.Error(), "no such relay blerg supported") }, diff --git a/core/services/ocrbootstrap/database_test.go b/core/services/ocrbootstrap/database_test.go index 74667f10fa6..dbae8439c7b 100644 --- a/core/services/ocrbootstrap/database_test.go +++ b/core/services/ocrbootstrap/database_test.go @@ -12,8 +12,8 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/ocr2/testhelpers" "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2/testhelpers" ) var ctx = context.Background() diff --git a/core/services/ocrbootstrap/delegate.go b/core/services/ocrbootstrap/delegate.go index 270b011aabc..29b9d384ee1 100644 --- a/core/services/ocrbootstrap/delegate.go +++ b/core/services/ocrbootstrap/delegate.go @@ -9,8 +9,8 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" "github.com/smartcontractkit/chainlink/core/services/relay" "github.com/smartcontractkit/chainlink/core/services/relay/types" ) @@ -21,9 +21,9 @@ type Delegate struct { db *sqlx.DB jobORM job.ORM peerWrapper *ocrcommon.SingletonPeerWrapper - cfg offchainreporting2.Config + cfg validate.Config lggr logger.Logger - relayer types.Relayer + relayer types.RelayerCtx } // NewDelegateBootstrap creates a new Delegate @@ -32,8 +32,8 @@ func NewDelegateBootstrap( jobORM job.ORM, peerWrapper *ocrcommon.SingletonPeerWrapper, lggr logger.Logger, - cfg offchainreporting2.Config, - relayer types.Relayer, + cfg validate.Config, + relayer types.RelayerCtx, ) *Delegate { return &Delegate{ db: db, @@ -51,7 +51,7 @@ func (d Delegate) JobType() job.Type { } // ServicesForSpec satisfies the job.Delegate interface. -func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err error) { +func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.ServiceCtx, err error) { spec := jobSpec.BootstrapSpec if spec == nil { return nil, errors.Errorf("Bootstrap.Delegate expects an *job.BootstrapSpec to be present, got %v", jobSpec) @@ -78,8 +78,7 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err return nil, errors.New("peerWrapper is not started. OCR2 jobs require a started and running peer. Did you forget to specify P2P_LISTEN_PORT?") } - loggerWith := d.lggr.With( - "OCRLogger", "true", + loggerWith := d.lggr.Named("OCR").With( "contractID", spec.ContractID, "jobName", jobSpec.Name.ValueOrZero(), "jobID", jobSpec.ID, @@ -89,7 +88,7 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err }) ocr2Spec := spec.AsOCR2Spec() - lc := offchainreporting2.ToLocalConfig(d.cfg, ocr2Spec) + lc := validate.ToLocalConfig(d.cfg, ocr2Spec) if err = ocr.SanityCheckLocalConfig(lc); err != nil { return nil, err } @@ -117,7 +116,7 @@ func (d Delegate) ServicesForSpec(jobSpec job.Job) (services []job.Service, err if err != nil { return nil, errors.Wrap(err, "error calling NewBootstrapNode") } - services = append(services, bootstrapper) + services = append(services, job.NewServiceAdapter(bootstrapper)) return services, nil } diff --git a/core/services/ocrbootstrap/validate_test.go b/core/services/ocrbootstrap/validate_test.go index 60323a2d4a8..6401e78b65a 100644 --- a/core/services/ocrbootstrap/validate_test.go +++ b/core/services/ocrbootstrap/validate_test.go @@ -6,7 +6,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/stretchr/testify/assert" - "github.com/test-go/testify/require" + "github.com/stretchr/testify/require" ) func TestValidateBootstrapSpec(t *testing.T) { diff --git a/core/services/ocrcommon/data_source_test.go b/core/services/ocrcommon/data_source_test.go index 5735f5c54c6..8da015baef4 100644 --- a/core/services/ocrcommon/data_source_test.go +++ b/core/services/ocrcommon/data_source_test.go @@ -1,15 +1,15 @@ package ocrcommon_test import ( - "context" "testing" - "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/core/services/pipeline" pipelinemocks "github.com/smartcontractkit/chainlink/core/services/pipeline/mocks" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -33,7 +33,7 @@ func Test_InMemoryDataSource(t *testing.T) { }, nil) ds := ocrcommon.NewInMemoryDataSource(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t)) - val, err := ds.Observe(context.TODO()) + val, err := ds.Observe(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, mockValue, val.String()) // returns expected value after pipeline run } @@ -53,7 +53,7 @@ func Test_NewDataSourceV2(t *testing.T) { resChan := make(chan pipeline.Run, 100) ds := ocrcommon.NewDataSourceV2(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t), resChan) - val, err := ds.Observe(context.TODO()) + val, err := ds.Observe(testutils.Context(t)) require.NoError(t, err) assert.Equal(t, mockValue, val.String()) // returns expected value after pipeline run assert.Equal(t, pipeline.Run{}, <-resChan) // expected data properly passed to channel diff --git a/core/services/ocrcommon/discoverer_database.go b/core/services/ocrcommon/discoverer_database.go index 973c044ef66..0a855665575 100644 --- a/core/services/ocrcommon/discoverer_database.go +++ b/core/services/ocrcommon/discoverer_database.go @@ -29,7 +29,7 @@ func NewDiscovererDatabase(db *sql.DB, peerID p2ppeer.ID) *DiscovererDatabase { // announcement (value). func (d *DiscovererDatabase) StoreAnnouncement(ctx context.Context, peerID string, ann []byte) error { _, err := d.db.ExecContext(ctx, ` -INSERT INTO offchainreporting_discoverer_announcements (local_peer_id, remote_peer_id, ann, created_at, updated_at) +INSERT INTO ocr_discoverer_announcements (local_peer_id, remote_peer_id, ann, created_at, updated_at) VALUES ($1,$2,$3,NOW(),NOW()) ON CONFLICT (local_peer_id, remote_peer_id) DO UPDATE SET ann = EXCLUDED.ann, updated_at = EXCLUDED.updated_at @@ -41,7 +41,7 @@ updated_at = EXCLUDED.updated_at // keyed by each announcement's corresponding peer ID. func (d *DiscovererDatabase) ReadAnnouncements(ctx context.Context, peerIDs []string) (map[string][]byte, error) { rows, err := d.db.QueryContext(ctx, ` -SELECT remote_peer_id, ann FROM offchainreporting_discoverer_announcements WHERE remote_peer_id = ANY($1) AND local_peer_id = $2`, pq.Array(peerIDs), d.peerID) +SELECT remote_peer_id, ann FROM ocr_discoverer_announcements WHERE remote_peer_id = ANY($1) AND local_peer_id = $2`, pq.Array(peerIDs), d.peerID) if err != nil { return nil, errors.Wrap(err, "DiscovererDatabase failed to ReadAnnouncements") } diff --git a/core/services/ocrcommon/peer_wrapper.go b/core/services/ocrcommon/peer_wrapper.go index 4c97fe5ec48..3d10f0bff6e 100644 --- a/core/services/ocrcommon/peer_wrapper.go +++ b/core/services/ocrcommon/peer_wrapper.go @@ -1,22 +1,26 @@ package ocrcommon import ( + "context" + p2ppeerstore "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/config" + p2ppeer "github.com/libp2p/go-libp2p-core/peer" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/keystore" - "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" - "github.com/smartcontractkit/chainlink/core/utils" ocrnetworking "github.com/smartcontractkit/libocr/networking" ocrnetworkingtypes "github.com/smartcontractkit/libocr/networking/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types" "go.uber.org/multierr" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/keystore" + "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/core/utils" ) type PeerWrapperConfig interface { @@ -101,16 +105,12 @@ func (p *SingletonPeerWrapper) IsStarted() bool { return p.State() == utils.StartStopOnce_Started } -func (p *SingletonPeerWrapper) Start() error { +// Start starts SingletonPeerWrapper. +func (p *SingletonPeerWrapper) Start(context.Context) error { return p.StartOnce("SingletonPeerWrapper", func() error { - // If there are no keys, permit the peer to start without a key - // TODO(https://app.shortcut.com/chainlinklabs/story/22677): - // This appears only a requirement for the tests, normally the node - // always ensures a key is available on boot. We should update the tests - // but there is a lot of them... + // Peer wrapper panics if no p2p keys are present. if ks, err := p.keyStore.P2P().GetAll(); err == nil && len(ks) == 0 { - p.lggr.Warn("No P2P keys found in keystore. Peer wrapper will not be fully initialized") - return nil + return errors.Errorf("No P2P keys found in keystore. Peer wrapper will not be fully initialized") } key, err := p.keyStore.P2P().GetOrFirst(p.config.P2PPeerID()) if err != nil { diff --git a/core/services/ocrcommon/peer_wrapper_test.go b/core/services/ocrcommon/peer_wrapper_test.go index 8a2cac1548b..5203996296d 100644 --- a/core/services/ocrcommon/peer_wrapper_test.go +++ b/core/services/ocrcommon/peer_wrapper_test.go @@ -3,22 +3,27 @@ package ocrcommon_test import ( "testing" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/ocrcommon" + "gopkg.in/guregu/null.v4" p2ppeer "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/stretchr/testify/require" ) func Test_SingletonPeerWrapper_Start(t *testing.T) { t.Parallel() - cfg := configtest.NewTestGeneralConfig(t) + cfg := configtest.NewTestGeneralConfigWithOverrides(t, configtest.GeneralConfigOverrides{ + P2PEnabled: null.BoolFrom(true), + }) db := pgtest.NewSqlxDB(t) require.NoError(t, utils.JustError(db.Exec(`DELETE FROM encrypted_key_rings`))) @@ -26,11 +31,10 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { peerID, err := p2ppeer.Decode("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") require.NoError(t, err) - t.Run("with no p2p keys returns nil", func(t *testing.T) { + t.Run("with no p2p keys returns error", func(t *testing.T) { keyStore := cltest.NewKeyStore(t, db, cfg) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, cfg, db, logger.TestLogger(t)) - - require.NoError(t, pw.Start()) + require.Contains(t, pw.Start(testutils.Context(t)).Error(), "No P2P keys found in keystore. Peer wrapper will not be fully initialized") }) var k p2pkey.KeyV2 @@ -46,7 +50,7 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { pw := ocrcommon.NewSingletonPeerWrapper(keyStore, cfg, db, logger.TestLogger(t)) - require.NoError(t, pw.Start(), "foo") + require.NoError(t, pw.Start(testutils.Context(t)), "foo") require.Equal(t, k.PeerID(), pw.PeerID) }) @@ -57,7 +61,7 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { pw := ocrcommon.NewSingletonPeerWrapper(keyStore, cfg, db, logger.TestLogger(t)) - require.Contains(t, pw.Start().Error(), "unable to find P2P key with id") + require.Contains(t, pw.Start(testutils.Context(t)).Error(), "unable to find P2P key with id") }) var k2 p2pkey.KeyV2 @@ -73,7 +77,7 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { pw := ocrcommon.NewSingletonPeerWrapper(keyStore, cfg, db, logger.TestLogger(t)) - require.NoError(t, pw.Start(), "foo") + require.NoError(t, pw.Start(testutils.Context(t)), "foo") require.Equal(t, k2.PeerID(), pw.PeerID) }) @@ -84,6 +88,6 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { pw := ocrcommon.NewSingletonPeerWrapper(keyStore, cfg, db, logger.TestLogger(t)) - require.Contains(t, pw.Start().Error(), "unable to find P2P key with id") + require.Contains(t, pw.Start(testutils.Context(t)).Error(), "unable to find P2P key with id") }) } diff --git a/core/services/ocrcommon/run_saver.go b/core/services/ocrcommon/run_saver.go index ec7e8e7893f..e9a36eb9add 100644 --- a/core/services/ocrcommon/run_saver.go +++ b/core/services/ocrcommon/run_saver.go @@ -1,6 +1,8 @@ package ocrcommon import ( + "context" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/utils" @@ -26,7 +28,8 @@ func NewResultRunSaver(runResults <-chan pipeline.Run, pipelineRunner pipeline.R } } -func (r *RunResultSaver) Start() error { +// Start starts RunResultSaver. +func (r *RunResultSaver) Start(context.Context) error { return r.StartOnce("RunResultSaver", func() error { go func() { for { diff --git a/core/services/ocrcommon/run_saver_test.go b/core/services/ocrcommon/run_saver_test.go index f1a898457da..602b19fd1bf 100644 --- a/core/services/ocrcommon/run_saver_test.go +++ b/core/services/ocrcommon/run_saver_test.go @@ -3,11 +3,13 @@ package ocrcommon import ( "testing" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/pipeline/mocks" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) func TestRunSaver(t *testing.T) { @@ -19,7 +21,7 @@ func TestRunSaver(t *testing.T) { make(chan struct{}), logger.TestLogger(t), ) - require.NoError(t, rs.Start()) + require.NoError(t, rs.Start(testutils.Context(t))) for i := 0; i < 100; i++ { d := i pipelineRunner.On("InsertFinishedRun", mock.Anything, mock.Anything, mock.Anything, mock.Anything). diff --git a/core/services/ocrcommon/transmitter.go b/core/services/ocrcommon/transmitter.go index a9f8937a10c..f76cdb2ad01 100644 --- a/core/services/ocrcommon/transmitter.go +++ b/core/services/ocrcommon/transmitter.go @@ -6,12 +6,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/services/pg" ) type txManager interface { - CreateEthTransaction(newTx bulletprooftxmanager.NewTx, qopts ...pg.QOpt) (etx bulletprooftxmanager.EthTx, err error) + CreateEthTransaction(newTx txmgr.NewTx, qopts ...pg.QOpt) (etx txmgr.EthTx, err error) } type Transmitter interface { @@ -23,12 +23,12 @@ type transmitter struct { txm txManager fromAddress common.Address gasLimit uint64 - strategy bulletprooftxmanager.TxStrategy - checker bulletprooftxmanager.TransmitCheckerSpec + strategy txmgr.TxStrategy + checker txmgr.TransmitCheckerSpec } // NewTransmitter creates a new eth transmitter -func NewTransmitter(txm txManager, fromAddress common.Address, gasLimit uint64, strategy bulletprooftxmanager.TxStrategy, checker bulletprooftxmanager.TransmitCheckerSpec) Transmitter { +func NewTransmitter(txm txManager, fromAddress common.Address, gasLimit uint64, strategy txmgr.TxStrategy, checker txmgr.TransmitCheckerSpec) Transmitter { return &transmitter{ txm: txm, fromAddress: fromAddress, @@ -39,7 +39,7 @@ func NewTransmitter(txm txManager, fromAddress common.Address, gasLimit uint64, } func (t *transmitter) CreateEthTransaction(ctx context.Context, toAddress common.Address, payload []byte) error { - _, err := t.txm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err := t.txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: t.fromAddress, ToAddress: toAddress, EncodedPayload: payload, diff --git a/core/services/ocrcommon/transmitter_test.go b/core/services/ocrcommon/transmitter_test.go index e9f8fe12454..6eb3e5f30ec 100644 --- a/core/services/ocrcommon/transmitter_test.go +++ b/core/services/ocrcommon/transmitter_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -25,19 +25,19 @@ func Test_Transmitter_CreateEthTransaction(t *testing.T) { gasLimit := uint64(1000) toAddress := testutils.NewAddress() payload := []byte{1, 2, 3} - txm := new(bptxmmocks.TxManager) - strategy := new(bptxmmocks.TxStrategy) + txm := new(txmmocks.TxManager) + strategy := new(txmmocks.TxStrategy) - transmitter := ocrcommon.NewTransmitter(txm, fromAddress, gasLimit, strategy, bulletprooftxmanager.TransmitCheckerSpec{}) + transmitter := ocrcommon.NewTransmitter(txm, fromAddress, gasLimit, strategy, txmgr.TransmitCheckerSpec{}) - txm.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txm.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: fromAddress, ToAddress: toAddress, EncodedPayload: payload, GasLimit: gasLimit, Meta: nil, Strategy: strategy, - }, mock.Anything).Return(bulletprooftxmanager.EthTx{}, nil).Once() + }, mock.Anything).Return(txmgr.EthTx{}, nil).Once() require.NoError(t, transmitter.CreateEthTransaction(context.Background(), toAddress, payload)) txm.AssertExpectations(t) diff --git a/core/services/periodicbackup/backup.go b/core/services/periodicbackup/backup.go index 17d9ebf3c0d..4033da70eb9 100644 --- a/core/services/periodicbackup/backup.go +++ b/core/services/periodicbackup/backup.go @@ -1,6 +1,7 @@ package periodicbackup import ( + "context" "fmt" "io/ioutil" "net/url" @@ -10,6 +11,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" @@ -36,7 +38,7 @@ type backupResult struct { type ( DatabaseBackup interface { - services.Service + services.ServiceCtx RunBackup(version string) error } @@ -89,7 +91,8 @@ func NewDatabaseBackup(config Config, lggr logger.Logger) (DatabaseBackup, error }, nil } -func (backup *databaseBackup) Start() error { +// Start starts DatabaseBackup. +func (backup *databaseBackup) Start(context.Context) error { return backup.StartOnce("DatabaseBackup", func() (err error) { ticker := time.NewTicker(backup.frequency) if backup.frequency == 0 { @@ -191,7 +194,8 @@ func (backup *databaseBackup) runBackup(version string) (*backupResult, error) { maskedArguments: maskedArgs, pgDumpArguments: args, } - if ee, ok := err.(*exec.ExitError); ok { + var ee *exec.ExitError + if errors.As(err, &ee) { return partialResult, errors.Wrapf(err, "pg_dump failed with output: %s", string(ee.Stderr)) } return partialResult, errors.Wrap(err, "pg_dump failed") diff --git a/core/services/periodicbackup/backup_test.go b/core/services/periodicbackup/backup_test.go index 5c80abe36a5..6d8ef76388a 100644 --- a/core/services/periodicbackup/backup_test.go +++ b/core/services/periodicbackup/backup_test.go @@ -7,14 +7,17 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/config" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func mustNewDatabaseBackup(t *testing.T, config Config) *databaseBackup { + testutils.SkipShortDB(t) b, err := NewDatabaseBackup(config, logger.TestLogger(t)) require.NoError(t, err) return b.(*databaseBackup) diff --git a/core/services/pg/advisory_lock.go b/core/services/pg/advisory_lock.go index 73d979c8b50..874126b8215 100644 --- a/core/services/pg/advisory_lock.go +++ b/core/services/pg/advisory_lock.go @@ -122,7 +122,7 @@ func (l *advisoryLock) checkoutConn(ctx context.Context) (err error) { // getLock obtains exclusive session level advisory lock if available. // It will either obtain the lock and return true, or return false if the lock cannot be acquired immediately. func (l *advisoryLock) getLock(ctx context.Context) (locked bool, err error) { - l.logger.Trace("Taking advisory lock") + l.logger.Debug("Taking advisory lock") sqlQuery := "SELECT pg_try_advisory_lock($1)" err = l.conn.QueryRowContext(ctx, sqlQuery, l.id).Scan(&locked) return locked, errors.WithStack(err) diff --git a/core/services/pg/event_broadcaster.go b/core/services/pg/event_broadcaster.go index b4b6edfd920..10c441c3454 100644 --- a/core/services/pg/event_broadcaster.go +++ b/core/services/pg/event_broadcaster.go @@ -24,7 +24,7 @@ import ( // EventBroadcaster opaquely manages a collection of Postgres event listeners // and broadcasts events to subscribers (with an optional payload filter). type EventBroadcaster interface { - services.Service + services.ServiceCtx Subscribe(channel, payloadFilter string) (Subscription, error) Notify(channel string, payload string) error } @@ -69,7 +69,8 @@ func NewEventBroadcaster(uri url.URL, minReconnectInterval time.Duration, maxRec } } -func (b *eventBroadcaster) Start() error { +// Start starts EventBroadcaster. +func (b *eventBroadcaster) Start(context.Context) error { return b.StartOnce("Postgres event broadcaster", func() (err error) { // Explicitly using the lib/pq for notifications so we use the postgres driverName // and NOT pgx. @@ -163,7 +164,7 @@ func (b *eventBroadcaster) Subscribe(channel, payloadFilter string) (Subscriptio queue: utils.NewBoundedQueue(1000), chEvents: make(chan Event), chDone: make(chan struct{}), - lggr: b.lggr, + lggr: logger.Sugared(b.lggr), } sub.processQueueWorker = utils.NewSleeperTask( utils.SleeperFuncTask(sub.processQueue, "SubscriptionQueueProcessor"), @@ -236,7 +237,7 @@ type subscription struct { processQueueWorker utils.SleeperTask chEvents chan Event chDone chan struct{} - lggr logger.Logger + lggr logger.SugaredLogger } var _ Subscription = (*subscription)(nil) @@ -259,7 +260,7 @@ func (sub *subscription) processQueue() { for !sub.queue.Empty() { event, ok := sub.queue.Take().(Event) if !ok { - sub.lggr.Errorf("Postgres event broadcaster subscription expected an Event, got %T", event) + sub.lggr.AssumptionViolationf("Postgres event broadcaster subscription expected an Event, got %T", event) continue } select { @@ -300,9 +301,16 @@ func NewNullEventBroadcaster() *NullEventBroadcaster { var _ EventBroadcaster = &NullEventBroadcaster{} -func (*NullEventBroadcaster) Start() error { return nil } -func (*NullEventBroadcaster) Close() error { return nil } -func (*NullEventBroadcaster) Ready() error { return nil } +// Start does no-op. +func (*NullEventBroadcaster) Start(context.Context) error { return nil } + +// Close does no-op. +func (*NullEventBroadcaster) Close() error { return nil } + +// Ready does no-op. +func (*NullEventBroadcaster) Ready() error { return nil } + +// Healthy does no-op. func (*NullEventBroadcaster) Healthy() error { return nil } func (ne *NullEventBroadcaster) Subscribe(channel, payloadFilter string) (Subscription, error) { diff --git a/core/services/pg/event_broadcaster_test.go b/core/services/pg/event_broadcaster_test.go index 65714780f2d..a3809da8448 100644 --- a/core/services/pg/event_broadcaster_test.go +++ b/core/services/pg/event_broadcaster_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/onsi/gomega" @@ -18,7 +19,7 @@ func TestEventBroadcaster(t *testing.T) { config, _ := heavyweight.FullTestDB(t, "event_broadcaster", true, false) eventBroadcaster := cltest.NewEventBroadcaster(t, config.DatabaseURL()) - require.NoError(t, eventBroadcaster.Start()) + require.NoError(t, eventBroadcaster.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, eventBroadcaster.Close()) }) t.Run("doesn't broadcast unrelated events (no payload filter)", func(t *testing.T) { diff --git a/core/services/pg/locked_db_test.go b/core/services/pg/locked_db_test.go index 1dfbec2468d..682186520bc 100644 --- a/core/services/pg/locked_db_test.go +++ b/core/services/pg/locked_db_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/pg" @@ -13,6 +14,7 @@ import ( ) func TestLockedDB_HappyPath(t *testing.T) { + testutils.SkipShortDB(t) config := cltest.NewTestGeneralConfig(t) config.Overrides.DatabaseLockingMode = null.StringFrom("dual") lggr := logger.TestLogger(t) @@ -28,6 +30,7 @@ func TestLockedDB_HappyPath(t *testing.T) { } func TestLockedDB_ContextCancelled(t *testing.T) { + testutils.SkipShortDB(t) config := cltest.NewTestGeneralConfig(t) config.Overrides.DatabaseLockingMode = null.StringFrom("dual") lggr := logger.TestLogger(t) @@ -41,6 +44,7 @@ func TestLockedDB_ContextCancelled(t *testing.T) { } func TestLockedDB_OpenTwice(t *testing.T) { + testutils.SkipShortDB(t) config := cltest.NewTestGeneralConfig(t) config.Overrides.DatabaseLockingMode = null.StringFrom("lease") lggr := logger.TestLogger(t) @@ -56,6 +60,7 @@ func TestLockedDB_OpenTwice(t *testing.T) { } func TestLockedDB_TwoInstances(t *testing.T) { + testutils.SkipShortDB(t) config := cltest.NewTestGeneralConfig(t) config.Overrides.DatabaseLockingMode = null.StringFrom("dual") lggr := logger.TestLogger(t) @@ -77,6 +82,7 @@ func TestLockedDB_TwoInstances(t *testing.T) { } func TestOpenUnlockedDB(t *testing.T) { + testutils.SkipShortDB(t) config := cltest.NewTestGeneralConfig(t) lggr := logger.TestLogger(t) diff --git a/core/services/pg/mocks/event_broadcaster.go b/core/services/pg/mocks/event_broadcaster.go index f6649e6c6c3..873b908689a 100644 --- a/core/services/pg/mocks/event_broadcaster.go +++ b/core/services/pg/mocks/event_broadcaster.go @@ -3,6 +3,8 @@ package mocks import ( + context "context" + pg "github.com/smartcontractkit/chainlink/core/services/pg" mock "github.com/stretchr/testify/mock" ) @@ -68,13 +70,13 @@ func (_m *EventBroadcaster) Ready() error { return r0 } -// Start provides a mock function with given fields: -func (_m *EventBroadcaster) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *EventBroadcaster) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/services/pg/q.go b/core/services/pg/q.go index 58817ea3d9f..36922a0dea7 100644 --- a/core/services/pg/q.go +++ b/core/services/pg/q.go @@ -4,8 +4,6 @@ import ( "context" "database/sql" "fmt" - "log" - "os" "strings" "time" @@ -68,6 +66,14 @@ func WithParentCtx(ctx context.Context) func(q *Q) { } } +// WithLongQueryTimeout prevents the usage of the `DefaultQueryTimeout` duration and uses `OneMinuteQueryTimeout` instead +// Some queries need to take longer when operating over big chunks of data, like deleting jobs, but we need to keep some upper bound timeout +func WithLongQueryTimeout() func(q *Q) { + return func(q *Q) { + q.QueryTimeout = LongQueryTimeout + } +} + // MergeCtx allows callers to combine a ctx with a previously set parent context // Responsibility for cancelling the passed context lies with caller func MergeCtx(fn func(parentCtx context.Context) context.Context) func(q *Q) { @@ -77,18 +83,6 @@ func MergeCtx(fn func(parentCtx context.Context) context.Context) func(q *Q) { } var _ Queryer = Q{} -var slowSqlThreshold = time.Second - -func init() { - slowSqlThresholdStr := os.Getenv("SLOW_SQL_THRESHOLD") - if len(slowSqlThresholdStr) > 0 { - d, err := time.ParseDuration(slowSqlThresholdStr) - if err != nil { - log.Fatalf("failed to parse SLOW_SQL_THRESHOLD: %s", err) - } - slowSqlThreshold = d - } -} // Q wraps an underlying queryer (either a *sqlx.DB or a *sqlx.Tx) // @@ -105,10 +99,11 @@ func init() { // can do. type Q struct { Queryer - ParentCtx context.Context - db *sqlx.DB - logger logger.Logger - config LogConfig + ParentCtx context.Context + db *sqlx.DB + logger logger.Logger + config LogConfig + QueryTimeout time.Duration } func NewQ(db *sqlx.DB, logger logger.Logger, config LogConfig, qopts ...QOpt) (q Q) { @@ -141,6 +136,14 @@ func (q Q) WithOpts(qopts ...QOpt) Q { } func (q Q) Context() (context.Context, context.CancelFunc) { + if q.QueryTimeout > 0 { + ctx := q.ParentCtx + if ctx == nil { + ctx = context.Background() + } + return context.WithTimeout(ctx, q.QueryTimeout) + } + if q.ParentCtx == nil { return DefaultQueryCtx() } @@ -268,7 +271,7 @@ func (q Q) logSqlQuery(query string, args ...interface{}) { } func (q Q) withLogError(err error) error { - if err != nil && err != sql.ErrNoRows && q.config != nil && q.config.LogSQL() { + if err != nil && !errors.Is(err, sql.ErrNoRows) && q.config != nil && q.config.LogSQL() { q.logger.Errorf("SQL ERROR: %v", err) } return err @@ -279,7 +282,13 @@ func (q Q) postSqlLog(ctx context.Context, begin time.Time) { if ctx.Err() != nil { q.logger.Debugf("SQL CONTEXT CANCELLED: %d ms, err=%v", elapsed.Milliseconds(), ctx.Err()) } - if slowSqlThreshold > 0 && elapsed > slowSqlThreshold { + + timeout := q.QueryTimeout + if timeout <= 0 { + timeout = DefaultQueryTimeout + } + slowThreshold := timeout / 10 + if slowThreshold > 0 && elapsed > slowThreshold { q.logger.Warnf("SLOW SQL QUERY: %d ms", elapsed.Milliseconds()) } } diff --git a/core/services/pg/transaction.go b/core/services/pg/transaction.go index ea0896d9492..618c13f6cb6 100644 --- a/core/services/pg/transaction.go +++ b/core/services/pg/transaction.go @@ -111,7 +111,7 @@ func sqlxTransactionQ(ctx context.Context, db TxBeginner, lggr logger.Logger, fn panic(fmt.Sprintf("panic in transaction; aborting rollback that took longer than 10s: %s", p)) } } else if err != nil { - lggr.Debugf("Error in transaction, rolling back: %s", err) + lggr.Errorf("Error in transaction, rolling back: %s", err) // An error occurred, rollback and return error if rerr := tx.Rollback(); rerr != nil { err = multierr.Combine(err, errors.WithStack(rerr)) diff --git a/core/services/pg/utils.go b/core/services/pg/utils.go index e1d781ea262..a14848f18d5 100644 --- a/core/services/pg/utils.go +++ b/core/services/pg/utils.go @@ -39,6 +39,8 @@ func init() { var ( // DefaultQueryTimeout is a reasonable upper bound for how long a SQL query should take DefaultQueryTimeout = 10 * time.Second + // LongQueryTimeout is a bigger upper bound for how long a SQL query should take + LongQueryTimeout = 1 * time.Minute // DefaultLockTimeout controls the max time we will wait for any kind of database lock. // It's good to set this to _something_ because waiting for locks forever is really bad. DefaultLockTimeout = 15 * time.Second diff --git a/core/services/pipeline/mocks/runner.go b/core/services/pipeline/mocks/runner.go index e4bf170bca0..25b27990ecb 100644 --- a/core/services/pipeline/mocks/runner.go +++ b/core/services/pipeline/mocks/runner.go @@ -181,13 +181,13 @@ func (_m *Runner) Run(ctx context.Context, run *pipeline.Run, l logger.Logger, s return r0, r1 } -// Start provides a mock function with given fields: -func (_m *Runner) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: _a0 +func (_m *Runner) Start(_a0 context.Context) error { + ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) } else { r0 = ret.Error(0) } diff --git a/core/services/pipeline/orm.go b/core/services/pipeline/orm.go index a90af49b21b..a0c5f551d19 100644 --- a/core/services/pipeline/orm.go +++ b/core/services/pipeline/orm.go @@ -14,10 +14,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" ) -var ( - ErrNoSuchBridge = errors.New("no such bridge exists") -) - //go:generate mockery --name ORM --output ./mocks/ --case=underscore type ORM interface { @@ -275,10 +271,42 @@ func (o *orm) InsertFinishedRun(run *Run, saveSuccessfulTaskRuns bool, qopts ... return errors.Wrap(err, "InsertFinishedRun failed") } +// DeleteRunsOlderThan deletes all pipeline_runs that have been finished for a certain threshold to free DB space func (o *orm) DeleteRunsOlderThan(ctx context.Context, threshold time.Duration) error { - q := o.q.WithOpts(pg.WithParentCtx(ctx)) - err := q.ExecQ(`DELETE FROM pipeline_runs WHERE finished_at < $1`, time.Now().Add(-threshold)) - return errors.Wrap(err, "DeleteRunsOlderThan failed") + // Addede 1 minute timeout to account for big databases + q := o.q.WithOpts(pg.WithParentCtx(ctx), pg.WithLongQueryTimeout()) + + err := pg.Batch(func(_, limit uint) (count uint, err error) { + result, cancel, err := q.ExecQIter(` +WITH batched_pipeline_runs AS ( + SELECT * FROM pipeline_runs + WHERE finished_at < ($1) + ORDER BY finished_at ASC + LIMIT $2 +) +DELETE FROM pipeline_runs +USING batched_pipeline_runs +WHERE pipeline_runs.id = batched_pipeline_runs.id`, + time.Now().Add(-threshold), + limit, + ) + defer cancel() + if err != nil { + return count, errors.Wrap(err, "DeleteRunsOlderThan failed to delete old pipeline_runs") + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return count, errors.Wrap(err, "DeleteRunsOlderThan failed to get rows affected") + } + + return uint(rowsAffected), err + }) + if err != nil { + return errors.Wrap(err, "DeleteRunsOlderThan failed") + } + + return nil } func (o *orm) FindRun(id int64) (r Run, err error) { diff --git a/core/services/pipeline/orm_test.go b/core/services/pipeline/orm_test.go index 18db7534357..eb9df983734 100644 --- a/core/services/pipeline/orm_test.go +++ b/core/services/pipeline/orm_test.go @@ -1,6 +1,7 @@ package pipeline_test import ( + "context" "testing" "time" @@ -349,3 +350,47 @@ func Test_PipelineORM_DeleteRun(t *testing.T) { _, err = orm.FindRun(run.ID) require.Error(t, err, "not found") } + +func Test_PipelineORM_DeleteRunsOlderThan(t *testing.T) { + _, orm := setupORM(t) + + var runsIds []int64 + + for i := 1; i <= 2000; i++ { + run := mustInsertAsyncRun(t, orm) + + now := time.Now() + + run.PipelineTaskRuns = []pipeline.TaskRun{ + // finished task + { + ID: uuid.NewV4(), + PipelineRunID: run.ID, + Type: "median", + DotID: "answer2", + Output: pipeline.JSONSerializable{Val: 1, Valid: true}, + CreatedAt: now, + FinishedAt: null.TimeFrom(now.Add(-1 * time.Second)), + }, + } + run.State = pipeline.RunStatusCompleted + run.FinishedAt = null.TimeFrom(now.Add(-1 * time.Second)) + run.Outputs = pipeline.JSONSerializable{Val: 1, Valid: true} + run.FatalErrors = pipeline.RunErrors{null.StringFrom("SOMETHING")} + + restart, err := orm.StoreRun(run) + assert.NoError(t, err) + // no new data, so we don't need a restart + assert.Equal(t, false, restart) + + runsIds = append(runsIds, run.ID) + } + + err := orm.DeleteRunsOlderThan(context.Background(), 1*time.Second) + assert.NoError(t, err) + + for _, runId := range runsIds { + _, err := orm.FindRun(runId) + require.Error(t, err, "not found") + } +} diff --git a/core/services/pipeline/runner.go b/core/services/pipeline/runner.go index 14e0a78c5cd..7bba39ec5d2 100644 --- a/core/services/pipeline/runner.go +++ b/core/services/pipeline/runner.go @@ -25,7 +25,7 @@ import ( //go:generate mockery --name Runner --output ./mocks/ --case=underscore type Runner interface { - services.Service + services.ServiceCtx // Run is a blocking call that will execute the run until no further progress can be made. // If `incomplete` is true, the run is only partially complete and is suspended, awaiting to be resumed when more data comes in. @@ -112,7 +112,8 @@ func NewRunner(orm ORM, config Config, chainSet evm.ChainSet, ethks ETHKeyStore, return r } -func (r *runner) Start() error { +// Start starts Runner. +func (r *runner) Start(context.Context) error { return r.StartOnce("PipelineRunner", func() error { r.wgDone.Add(2) go r.scheduleUnfinishedRuns() @@ -143,7 +144,7 @@ func (r *runner) runReaperLoop() { return } - runReaperTicker := time.NewTicker(r.config.JobPipelineReaperInterval()) + runReaperTicker := time.NewTicker(utils.WithJitter(r.config.JobPipelineReaperInterval())) defer runReaperTicker.Stop() for { select { @@ -151,6 +152,7 @@ func (r *runner) runReaperLoop() { return case <-runReaperTicker.C: r.runReaperWorker.WakeUp() + runReaperTicker.Reset(utils.WithJitter(r.config.JobPipelineReaperInterval())) } } } @@ -262,7 +264,8 @@ func (r *runner) run( vars Vars, l logger.Logger, ) (TaskRunResults, error) { - l.Debugw("Initiating tasks for pipeline run of spec", "job ID", run.PipelineSpec.JobID, "job name", run.PipelineSpec.JobName) + l = l.With("jobID", run.PipelineSpec.JobID, "jobName", run.PipelineSpec.JobName) + l.Debug("Initiating tasks for pipeline run of spec") scheduler := newScheduler(pipeline, run, vars, l) go scheduler.Run() @@ -387,7 +390,7 @@ func (r *runner) executeTaskRun(ctx context.Context, spec Spec, taskRun *memoryT // below. It has already been changed several times trying to "fix" a bug, // but actually introducing new ones. Please leave it as-is unless you have // an extremely good reason to change it. - ctx, cancel := utils.CombinedContext(ctx, r.chStop) + ctx, cancel := utils.WithCloseChan(ctx, r.chStop) defer cancel() if taskTimeout, isSet := taskRun.task.TaskTimeout(); isSet && taskTimeout > 0 { ctx, cancel = context.WithTimeout(ctx, taskTimeout) @@ -576,13 +579,11 @@ func (r *runner) InsertFinishedRun(run *Run, saveSuccessfulTaskRuns bool, qopts } func (r *runner) runReaper() { - ctx, cancel := utils.CombinedContext(context.Background(), r.chStop) + ctx, cancel := utils.ContextFromChan(r.chStop) defer cancel() err := r.orm.DeleteRunsOlderThan(ctx, r.config.JobPipelineReaperThreshold()) - if ctx.Err() != nil { - return - } else if err != nil { + if err != nil { r.lggr.Errorw("Pipeline run reaper failed", "error", err) } } @@ -598,7 +599,7 @@ func (r *runner) scheduleUnfinishedRuns() { // immediately run reaper so we don't consider runs that are too old r.runReaper() - ctx, cancel := utils.CombinedContext(context.Background(), r.chStop) + ctx, cancel := utils.ContextFromChan(r.chStop) defer cancel() err := r.orm.GetUnfinishedRuns(ctx, now, func(run Run) error { diff --git a/core/services/pipeline/task.eth_call_test.go b/core/services/pipeline/task.eth_call_test.go index 7aeb04f2ce4..d1bee243315 100644 --- a/core/services/pipeline/task.eth_call_test.go +++ b/core/services/pipeline/task.eth_call_test.go @@ -13,8 +13,8 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/chains/evm" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" @@ -141,7 +141,7 @@ func TestETHCallTask(t *testing.T) { keyStore := new(keystoremocks.Eth) keyStore.Test(t) - txManager := new(bptxmmocks.TxManager) + txManager := new(txmmocks.TxManager) txManager.Test(t) db := pgtest.NewSqlxDB(t) diff --git a/core/services/pipeline/task.eth_tx.go b/core/services/pipeline/task.eth_tx.go index 6714e1a0560..35f7d737df3 100644 --- a/core/services/pipeline/task.eth_tx.go +++ b/core/services/pipeline/task.eth_tx.go @@ -11,7 +11,7 @@ import ( "go.uber.org/multierr" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/null" ) @@ -112,9 +112,9 @@ func (t *ETHTxTask) Run(_ context.Context, lggr logger.Logger, vars Vars, inputs } // NOTE: This can be easily adjusted later to allow job specs to specify the details of which strategy they would like - strategy := bulletprooftxmanager.NewSendEveryStrategy() + strategy := txmgr.NewSendEveryStrategy() - newTx := bulletprooftxmanager.NewTx{ + newTx := txmgr.NewTx{ FromAddress: fromAddr, ToAddress: common.Address(toAddr), EncodedPayload: []byte(data), @@ -142,8 +142,8 @@ func (t *ETHTxTask) Run(_ context.Context, lggr logger.Logger, vars Vars, inputs return Result{Value: nil}, runInfo } -func decodeMeta(metaMap MapParam) (*bulletprooftxmanager.EthTxMeta, error) { - var txMeta bulletprooftxmanager.EthTxMeta +func decodeMeta(metaMap MapParam) (*txmgr.EthTxMeta, error) { + var txMeta txmgr.EthTxMeta metaDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Result: &txMeta, ErrorUnused: true, @@ -172,8 +172,8 @@ func decodeMeta(metaMap MapParam) (*bulletprooftxmanager.EthTxMeta, error) { return &txMeta, nil } -func decodeTransmitChecker(checkerMap MapParam) (bulletprooftxmanager.TransmitCheckerSpec, error) { - var transmitChecker bulletprooftxmanager.TransmitCheckerSpec +func decodeTransmitChecker(checkerMap MapParam) (txmgr.TransmitCheckerSpec, error) { + var transmitChecker txmgr.TransmitCheckerSpec checkerDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Result: &transmitChecker, ErrorUnused: true, diff --git a/core/services/pipeline/task.eth_tx_test.go b/core/services/pipeline/task.eth_tx_test.go index a6f98ce8105..27e5acfb716 100644 --- a/core/services/pipeline/task.eth_tx_test.go +++ b/core/services/pipeline/task.eth_tx_test.go @@ -11,8 +11,8 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" @@ -35,7 +35,7 @@ func TestETHTxTask(t *testing.T) { transmitChecker string vars pipeline.Vars inputs []pipeline.Result - setupClientMocks func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) + setupClientMocks func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) expected interface{} expectedErrorCause error expectedErrorContains string @@ -53,26 +53,26 @@ func TestETHTxTask(t *testing.T) { `{"CheckerType": "vrf_v2", "VRFCoordinatorAddress": "0x2E396ecbc8223Ebc16EC45136228AE5EDB649943"}`, pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - Checker: bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeVRFV2, + Strategy: txmgr.SendEveryStrategy{}, + Checker: txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: common.HexToAddress("0x2E396ecbc8223Ebc16EC45136228AE5EDB649943"), }, - }).Return(bulletprooftxmanager.EthTx{}, nil) + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -96,22 +96,22 @@ func TestETHTxTask(t *testing.T) { "requestTxHash": common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8"), }), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, nil) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -137,22 +137,22 @@ func TestETHTxTask(t *testing.T) { }, }), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, nil) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -178,22 +178,22 @@ func TestETHTxTask(t *testing.T) { }, }), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress").Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, nil) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -209,22 +209,22 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{} + txMeta := &txmgr.EthTxMeta{} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, nil) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -240,22 +240,22 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(999) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, nil) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, }, @@ -281,7 +281,7 @@ func TestETHTxTask(t *testing.T) { }, }), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) keyStore.On("GetRoundRobinAddress").Return(nil, errors.New("uh oh")) }, @@ -299,22 +299,22 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") to := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") data := []byte("foobar") gasLimit := uint64(12345) - txMeta := &bulletprooftxmanager.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} + txMeta := &txmgr.EthTxMeta{JobID: 321, RequestID: common.HexToHash("0x5198616554d738d9485d1a7cf53b2f33e09c3bbc8fe9ac0020bd672cd2bc15d2"), RequestTxHash: common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8")} keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", bulletprooftxmanager.NewTx{ + txManager.On("CreateEthTransaction", txmgr.NewTx{ FromAddress: from, ToAddress: to, EncodedPayload: data, GasLimit: gasLimit, Meta: txMeta, - Strategy: bulletprooftxmanager.SendEveryStrategy{}, - }).Return(bulletprooftxmanager.EthTx{}, errors.New("uh oh")) + Strategy: txmgr.SendEveryStrategy{}, + }).Return(txmgr.EthTx{}, errors.New("uh oh")) }, nil, pipeline.ErrTaskRunFailed, "while creating transaction", pipeline.RunInfo{IsRetryable: true}, }, @@ -330,7 +330,7 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) }, nil, pipeline.ErrBadInput, "txMeta", pipeline.RunInfo{}, @@ -347,7 +347,7 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) }, nil, pipeline.ErrBadInput, "txMeta", pipeline.RunInfo{}, @@ -364,7 +364,7 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) }, nil, pipeline.ErrParameterEmpty, "to", pipeline.RunInfo{}, @@ -381,7 +381,7 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), []pipeline.Result{{Error: errors.New("uh oh")}}, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { }, nil, pipeline.ErrTooManyErrors, "task inputs", pipeline.RunInfo{}, }, @@ -397,13 +397,13 @@ func TestETHTxTask(t *testing.T) { "", pipeline.NewVarsFrom(nil), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { config.Overrides.GlobalEvmGasLimitDefault = null.IntFrom(999) from := common.HexToAddress("0x882969652440ccf14a5dbb9bd53eb21cb1e11e5c") keyStore.On("GetRoundRobinAddress", from).Return(from, nil) - txManager.On("CreateEthTransaction", mock.MatchedBy(func(tx bulletprooftxmanager.NewTx) bool { + txManager.On("CreateEthTransaction", mock.MatchedBy(func(tx txmgr.NewTx) bool { return tx.MinConfirmations == clnull.Uint32From(3) && tx.PipelineTaskRunID != nil - })).Return(bulletprooftxmanager.EthTx{}, nil) + })).Return(txmgr.EthTx{}, nil) }, nil, nil, "", pipeline.RunInfo{IsPending: true}, }, @@ -428,7 +428,7 @@ func TestETHTxTask(t *testing.T) { "evmChainID": "123", }), nil, - func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *bptxmmocks.TxManager) { + func(config *configtest.TestGeneralConfig, keyStore *keystoremocks.Eth, txManager *txmmocks.TxManager) { }, nil, nil, "chain not found", pipeline.RunInfo{IsRetryable: true}, }, @@ -453,7 +453,7 @@ func TestETHTxTask(t *testing.T) { keyStore := new(keystoremocks.Eth) keyStore.Test(t) - txManager := new(bptxmmocks.TxManager) + txManager := new(txmmocks.TxManager) txManager.Test(t) db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) diff --git a/core/services/pipeline/task.http.go b/core/services/pipeline/task.http.go index 088d4e40867..61f0fc790cf 100644 --- a/core/services/pipeline/task.http.go +++ b/core/services/pipeline/task.http.go @@ -85,7 +85,7 @@ func (t *HTTPTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, input responseBytes, statusCode, _, elapsed, err := makeHTTPRequest(requestCtx, lggr, method, url, requestData, allowUnrestrictedNetworkAccess, t.config.DefaultHTTPLimit()) if err != nil { - if errors.Cause(err) == ErrDisallowedIP { + if errors.Is(errors.Cause(err), ErrDisallowedIP) { err = errors.Wrap(err, "connections to local resources are disabled by default, if you are sure this is safe, you can enable on a per-task basis by setting allowUnrestrictedNetworkAccess=true in the pipeline task spec") } return Result{Error: err}, RunInfo{IsRetryable: isRetryableHTTPError(statusCode, err)} diff --git a/core/services/pipeline/task_params.go b/core/services/pipeline/task_params.go index b6e49fd22be..4ca9c2869be 100644 --- a/core/services/pipeline/task_params.go +++ b/core/services/pipeline/task_params.go @@ -31,7 +31,7 @@ func ResolveParam(out PipelineParamUnmarshaler, getters []GetterFunc) error { var found bool for _, get := range getters { val, err = get() - if errors.Cause(err) == ErrParameterEmpty { + if errors.Is(errors.Cause(err), ErrParameterEmpty) { continue } else if err != nil { return err diff --git a/core/services/promreporter/prom_reporter.go b/core/services/promreporter/prom_reporter.go index bc606332064..da3ad9e2765 100644 --- a/core/services/promreporter/prom_reporter.go +++ b/core/services/promreporter/prom_reporter.go @@ -109,7 +109,8 @@ func NewPromReporter(db *sql.DB, lggr logger.Logger, opts ...interface{}) *promR } } -func (pr *promReporter) Start() error { +// Start starts PromReporter. +func (pr *promReporter) Start(context.Context) error { return pr.StartOnce("PromReporter", func() error { pr.wgDone.Add(1) go pr.eventLoop() diff --git a/core/services/promreporter/prom_reporter_test.go b/core/services/promreporter/prom_reporter_test.go index 6e14e10a046..735bf5e5f97 100644 --- a/core/services/promreporter/prom_reporter_test.go +++ b/core/services/promreporter/prom_reporter_test.go @@ -7,16 +7,18 @@ import ( "time" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/promreporter" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "go.uber.org/atomic" ) func newHead() evmtypes.Head { @@ -43,7 +45,7 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { }). Return() - reporter.Start() + reporter.Start(testutils.Context(t)) defer reporter.Close() head := newHead() @@ -57,7 +59,7 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { t.Run("with unconfirmed eth_txes", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := cltest.NewTestGeneralConfig(t) - borm := cltest.NewBulletproofTxManagerORM(t, db, cfg) + borm := cltest.NewTxmORM(t, db, cfg) ethKeyStore := cltest.NewKeyStore(t, db, cfg).Eth() _, fromAddress := cltest.MustAddRandomKeyToKeystore(t, ethKeyStore) @@ -77,7 +79,7 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { }). Return() reporter := promreporter.NewPromReporter(db.DB, logger.TestLogger(t), backend, 10*time.Millisecond) - reporter.Start() + reporter.Start(testutils.Context(t)) defer reporter.Close() etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 0, fromAddress) @@ -117,7 +119,7 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { subscribeCalls.Inc() }). Return() - reporter.Start() + reporter.Start(testutils.Context(t)) defer reporter.Close() head := newHead() diff --git a/core/services/relay/delegate.go b/core/services/relay/delegate.go index 067fc696902..7675bf13b4e 100644 --- a/core/services/relay/delegate.go +++ b/core/services/relay/delegate.go @@ -1,16 +1,17 @@ package relay import ( + "context" "encoding/json" solanaGo "github.com/gagliardetto/solana-go" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" + "github.com/smartcontractkit/chainlink-solana/pkg/solana" + "github.com/smartcontractkit/chainlink-terra/pkg/terra" "go.uber.org/multierr" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-solana/pkg/solana" - "github.com/smartcontractkit/chainlink-terra/pkg/terra" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/relay/evm" @@ -23,13 +24,13 @@ var ( types.Solana: {}, types.Terra: {}, } - _ types.Relayer = &evm.Relayer{} - _ types.Relayer = &solana.Relayer{} - _ types.Relayer = &terra.Relayer{} + _ types.RelayerCtx = &evm.Relayer{} + _ types.RelayerCtx = &solana.Relayer{} + _ types.RelayerCtx = &terra.Relayer{} ) type delegate struct { - relayers map[types.Network]types.Relayer + relayers map[types.Network]types.RelayerCtx ks keystore.Master } @@ -38,22 +39,22 @@ type delegate struct { func NewDelegate(ks keystore.Master) *delegate { d := &delegate{ ks: ks, - relayers: map[types.Network]types.Relayer{}, + relayers: map[types.Network]types.RelayerCtx{}, } return d } // AddRelayer registers the relayer r, or a disabled placeholder if nil. // NOT THREAD SAFE -func (d delegate) AddRelayer(n types.Network, r types.Relayer) { +func (d delegate) AddRelayer(n types.Network, r types.RelayerCtx) { d.relayers[n] = r } -// A delegate relayer on start will start all relayers it manages. -func (d delegate) Start() error { +// Start starts all relayers it manages. +func (d delegate) Start(ctx context.Context) error { var err error for _, r := range d.relayers { - err = multierr.Combine(err, r.Start()) + err = multierr.Combine(err, r.Start(ctx)) } return err } @@ -91,11 +92,13 @@ type OCR2ProviderArgs struct { ContractID string TransmitterID null.String Relay types.Network - RelayConfig job.RelayConfig + RelayConfig job.JSONConfig IsBootstrapPeer bool + Plugin job.OCR2PluginType } -func (d delegate) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types.OCR2Provider, error) { +// NewOCR2Provider creates a new OCR2 provider instance. +func (d delegate) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types.OCR2ProviderCtx, error) { // We expect trusted input spec := s.(*OCR2ProviderArgs) choice := spec.Relay @@ -118,6 +121,7 @@ func (d delegate) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types ContractID: spec.ContractID, TransmitterID: spec.TransmitterID, ChainID: config.ChainID.ToInt(), + Plugin: spec.Plugin, }) case types.Solana: r, exists := d.relayers[types.Solana] diff --git a/core/services/relay/delegate_test.go b/core/services/relay/delegate_test.go index 4cd478f60bb..62ee1594f1d 100644 --- a/core/services/relay/delegate_test.go +++ b/core/services/relay/delegate_test.go @@ -8,6 +8,7 @@ import ( "github.com/pelletier/go-toml" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-solana/pkg/solana" @@ -27,10 +28,10 @@ import ( "github.com/smartcontractkit/chainlink/core/testdata/testspecs" ) -func makeOCR2JobSpecFromToml(t *testing.T, jobSpecToml string) job.OffchainReporting2OracleSpec { +func makeOCR2JobSpecFromToml(t *testing.T, jobSpecToml string) job.OCR2OracleSpec { t.Helper() - var ocr2spec job.OffchainReporting2OracleSpec + var ocr2spec job.OCR2OracleSpec err := toml.Unmarshal([]byte(jobSpecToml), &ocr2spec) require.NoError(t, err) @@ -51,12 +52,12 @@ func TestNewOCR2Provider(t *testing.T) { // setup terra mocks terraChain := new(terraMock.Chain) terraChain.On("Config").Return(terra.NewConfig(terradb.ChainCfg{}, lggr)) - terraChain.On("MsgEnqueuer").Return(new(terraMock.MsgEnqueuer)).Times(2) + terraChain.On("TxManager").Return(new(terraMock.TxManager)).Times(2) terraChain.On("Reader", "").Return(new(terraMock.Reader), nil).Once() terraChain.On("Reader", "some-test-node").Return(new(terraMock.Reader), nil).Once() terraChains := new(terraMock.ChainSet) - terraChains.On("Chain", "Chainlink-99").Return(terraChain, nil).Times(2) + terraChains.On("Chain", mock.Anything, "Chainlink-99").Return(terraChain, nil).Times(2) d := relay.NewDelegate(keystore) diff --git a/core/services/relay/evm/config_tracker.go b/core/services/relay/evm/config_tracker.go new file mode 100644 index 00000000000..0e4409c1d5b --- /dev/null +++ b/core/services/relay/evm/config_tracker.go @@ -0,0 +1,227 @@ +package evm + +import ( + "context" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink/core/chains" + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" + httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// ConfigTracker tracks the config of any contract implementing OCR2Abstract. +type ConfigTracker struct { + utils.StartStopOnce + lggr logger.Logger + client evmclient.Client + addr common.Address + contractABI abi.ABI + chainType chains.ChainType + + latestBlockHeight int64 + latestBlockHeightMu sync.RWMutex + + headBroadcaster httypes.HeadBroadcaster + unsubscribeHeads func() + + chStop chan struct{} +} + +// NewConfigTracker builds a new config tracker +func NewConfigTracker(lggr logger.Logger, contractABI abi.ABI, client evmclient.Client, addr common.Address, chainType chains.ChainType, headBroadcaster httypes.HeadBroadcaster) *ConfigTracker { + return &ConfigTracker{ + client: client, + addr: addr, + contractABI: contractABI, + chainType: chainType, + latestBlockHeight: -1, + latestBlockHeightMu: sync.RWMutex{}, + lggr: lggr, + headBroadcaster: headBroadcaster, + unsubscribeHeads: nil, + chStop: make(chan struct{}), + } +} + +// Start starts the config tracker in particular subscribing to the head broadcaster. +func (c *ConfigTracker) Start() error { + return c.StartOnce("ConfigTracker", func() (err error) { + var latestHead *evmtypes.Head + latestHead, c.unsubscribeHeads = c.headBroadcaster.Subscribe(c) + if latestHead != nil { + c.setLatestBlockHeight(*latestHead) + } + + return nil + }) +} + +// Close cancels and requests and unsubscribes from the head broadcaster +func (c *ConfigTracker) Close() error { + close(c.chStop) + c.unsubscribeHeads() + return nil +} + +// Notify not implemented +func (c *ConfigTracker) Notify() <-chan struct{} { + return nil +} + +func callContract(ctx context.Context, addr common.Address, contractABI abi.ABI, method string, args []interface{}, caller contractReader) ([]interface{}, error) { + input, err := contractABI.Pack(method, args...) + if err != nil { + return nil, err + } + output, err := caller.CallContract(ctx, ethereum.CallMsg{To: &addr, Data: input}, nil) + if err != nil { + return nil, err + } + return contractABI.Unpack(method, output) +} + +// LatestConfigDetails queries an OCR2Abstract contract for the latest config details +func (c *ConfigTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { + latestConfigDetails, err := callContract(ctx, c.addr, c.contractABI, "latestConfigDetails", nil, c.client) + if err != nil { + return 0, ocrtypes.ConfigDigest{}, err + } + // Panic on these conversions erroring, would mean a broken contract. + changedInBlock = uint64(*abi.ConvertType(latestConfigDetails[1], new(uint32)).(*uint32)) + configDigest = *abi.ConvertType(latestConfigDetails[2], new([32]byte)).(*[32]byte) + return +} + +// LatestConfig queries an OCR2Abstract contract for the latest config contents. +func (c *ConfigTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (ocrtypes.ContractConfig, error) { + topics, err := abi.MakeTopics([]interface{}{c.contractABI.Events["ConfigSet"].ID}) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + query := ethereum.FilterQuery{ + Addresses: []common.Address{c.addr}, + Topics: topics, + FromBlock: new(big.Int).SetUint64(changedInBlock), + ToBlock: new(big.Int).SetUint64(changedInBlock), + } + logs, err := c.client.FilterLogs(ctx, query) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + if len(logs) == 0 { + err = errors.New("Contract not configured yet") + c.lggr.Warnw(err.Error()) + return ocrtypes.ContractConfig{}, err + } + return parseConfigSet(c.contractABI, logs[len(logs)-1]) +} + +func parseConfigSet(a abi.ABI, log types.Log) (ocrtypes.ContractConfig, error) { + var changed struct { + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers []common.Address + Transmitters []common.Address + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + } + // Use bound contract solely for its unpack log logic + // which only uses the abi. + err := bind.NewBoundContract(common.Address{}, a, nil, nil, nil).UnpackLog(&changed, "ConfigSet", log) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + var transmitAccounts []ocrtypes.Account + for _, addr := range changed.Transmitters { + transmitAccounts = append(transmitAccounts, ocrtypes.Account(addr.Hex())) + } + var signers []ocrtypes.OnchainPublicKey + for _, addr := range changed.Signers { + addr := addr + signers = append(signers, addr[:]) + } + return ocrtypes.ContractConfig{ + ConfigDigest: changed.ConfigDigest, + ConfigCount: changed.ConfigCount, + Signers: signers, + Transmitters: transmitAccounts, + F: changed.F, + OnchainConfig: changed.OnchainConfig, + OffchainConfigVersion: changed.OffchainConfigVersion, + OffchainConfig: changed.OffchainConfig, + }, nil +} + +// Connect conforms to HeadTrackable +func (c *ConfigTracker) Connect(*evmtypes.Head) error { return nil } + +// OnNewLongestChain conformed to HeadTrackable and updates latestBlockHeight +func (c *ConfigTracker) OnNewLongestChain(_ context.Context, h *evmtypes.Head) { + c.setLatestBlockHeight(*h) +} + +func (c *ConfigTracker) setLatestBlockHeight(h evmtypes.Head) { + var num int64 + if h.L1BlockNumber.Valid { + num = h.L1BlockNumber.Int64 + } else { + num = h.Number + } + c.latestBlockHeightMu.Lock() + defer c.latestBlockHeightMu.Unlock() + if num > c.latestBlockHeight { + c.latestBlockHeight = num + } +} + +func (c *ConfigTracker) getLatestBlockHeight() int64 { + c.latestBlockHeightMu.RLock() + defer c.latestBlockHeightMu.RUnlock() + return c.latestBlockHeight +} + +// LatestBlockHeight returns the latest blockheight either from the cache or +// falling back to querying the node. +func (c *ConfigTracker) LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error) { + // We skip confirmation checking anyway on Optimism so there's no need to care + // about the block height; we have no way of getting the L1 block height anyway + if c.chainType != "" { + return 0, nil + } + latestBlockHeight := c.getLatestBlockHeight() + if latestBlockHeight >= 0 { + return uint64(latestBlockHeight), nil + } + + var cancel context.CancelFunc + ctx, cancel = utils.WithCloseChan(ctx, c.chStop) + defer cancel() + + c.lggr.Debugw("ConfigTracker: still waiting for first head, falling back to on-chain lookup") + h, err := c.client.HeadByNumber(ctx, nil) + if err != nil { + return 0, err + } + if h == nil { + return 0, errors.New("got nil head") + } + if h.L1BlockNumber.Valid { + return uint64(h.L1BlockNumber.Int64), nil + } + return uint64(h.Number), nil +} diff --git a/core/services/relay/evm/config_tracker_test.go b/core/services/relay/evm/config_tracker_test.go new file mode 100644 index 00000000000..f87db7388fc --- /dev/null +++ b/core/services/relay/evm/config_tracker_test.go @@ -0,0 +1,106 @@ +package evm_test + +import ( + "context" + "encoding/hex" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/relay/evm" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" +) + +func TestConfigTracker_LatestConfig(t *testing.T) { + lggr := logger.TestLogger(t) + c := new(evmmocks.Client) + contractABI, _ := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) + ct := evm.NewConfigTracker(lggr, contractABI, c, common.Address{}, "", nil) + + configSet, _ := hex.DecodeString( + "0000000000000000000000000000000000000000000000000000000000000000000168dbbc989af81ad798fdc0102dcd7608a0f3943f5372d70e066f4cc47aef0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000100a209e68e25ecf836a1563b34b0bd884de3c9c000000000000000000000000c9e3fe3a9b0ab463abc67f510001b52b3ee2fabc000000000000000000000000f9cbaa5e7d007a828c1d903ec827fa73c5813cf30000000000000000000000005dc70bd55d9a5300d8dfd2b50307ecf62eaef33600000000000000000000000000000000000000000000000000000000000000040000000000000000000000008ce423450190e1069f146b0d36ccef5a111c34ca00000000000000000000000027f93e255054e5435ed66ce29bba464f49104c970000000000000000000000000ada788ab72408cd016baed9966929157fe0888b0000000000000000000000004d6524c5afe74d68e0bc5c6edaf74a08423e13a90000000000000000000000000000000000000000000000000000000000000031018000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023b0880a8d6b907108094ebdc03188094ebdc032080cab5ee012880a8d6b90730033a04010101014220741efdafc6e2f6ea87b58e33619b15cd0f0fb8432c7c5b17bebe64a4d2744648422092806cf7f5ce0c8ac298bd8d168a91424601d1d1de9b36bff6cf3c44d159f4ef422035674e56b0e46e4bcab213c46ef1ba95a6cd9278440bf35ae5e358afd32c605742203108fb8086b6338e92519b7cd70846db44ad35613096feda392c661083a6708c4a34313244334b6f6f574a375a3176394469733351765261476d5a33666e4b79375646724b64717474585a5a56735a356d534b58716e4a34313244334b6f6f574753545944486f5a595148646759614437523161396232484e69466f4448656f4854706e4d6e6b7a783733444a34313244334b6f6f574c3971724150575253596e55757444587658527555316269585842424776315a7145726f6f737442354a39334a34313244334b6f6f574465437471455144436154744171697a47765367444d726f3979345574674d526d595659596f466641776939520a1080ade2042080ade2045880e1eb176080e1eb176880e1eb177080e1eb177880e1eb1782018c010a20befed1f74bcd9dc9342cd6106d78604a214d9904a37ed0b6058595f605e9783012201a1bf753c2505dab5204b284f1fefd7dc4eb83f6304664c9c2f43cbf425666601a10b16b9ed7f188a4381d81a6ae8d379abd1a10d6a941106211fcaf568ccc1493a0a2161a105a765837db073bd319cbd6e6cadfa9d91a1015cecfc264d0ad9567f7b5c3e44e0f950000000000") + c.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{{Topics: []common.Hash{contractABI.Events["ConfigSet"].ID}, Data: configSet}}, nil).Once() + cfg, err := ct.LatestConfig(context.Background(), 10) + require.NoError(t, err) + // Spot check a few values + assert.Equal(t, uint8(1), cfg.F) + assert.Equal(t, 4, len(cfg.Signers)) + assert.Equal(t, 4, len(cfg.Transmitters)) +} + +func Test_OCRContractTracker_LatestBlockHeight(t *testing.T) { + t.Parallel() + + t.Run("on L2 chains, always returns 0", func(t *testing.T) { + uni := newContractTrackerUni(t, evmtest.ChainOptimismMainnet(t)) + l, err := uni.configTracker.LatestBlockHeight(context.Background()) + require.NoError(t, err) + + assert.Equal(t, uint64(0), l) + }) + + t.Run("before first head incoming, looks up on-chain", func(t *testing.T) { + uni := newContractTrackerUni(t) + uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(&evmtypes.Head{Number: 42}, nil) + + l, err := uni.configTracker.LatestBlockHeight(context.Background()) + require.NoError(t, err) + + assert.Equal(t, uint64(42), l) + }) + + t.Run("Before first head incoming, on client error returns error", func(t *testing.T) { + uni := newContractTrackerUni(t) + uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(nil, nil).Once() + + _, err := uni.configTracker.LatestBlockHeight(context.Background()) + assert.EqualError(t, err, "got nil head") + + uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(nil, errors.New("bar")).Once() + + _, err = uni.configTracker.LatestBlockHeight(context.Background()) + assert.EqualError(t, err, "bar") + + uni.ec.AssertExpectations(t) + }) + + t.Run("after first head incoming, uses cached value", func(t *testing.T) { + uni := newContractTrackerUni(t) + + uni.configTracker.OnNewLongestChain(context.Background(), &evmtypes.Head{Number: 42}) + + l, err := uni.configTracker.LatestBlockHeight(context.Background()) + require.NoError(t, err) + + assert.Equal(t, uint64(42), l) + }) + + t.Run("if headbroadcaster has it, uses the given value on start", func(t *testing.T) { + uni := newContractTrackerUni(t) + + uni.hb.On("Subscribe", uni.configTracker).Return(&evmtypes.Head{Number: 42}, func() {}) + require.NoError(t, uni.configTracker.Start()) + + l, err := uni.configTracker.LatestBlockHeight(context.Background()) + require.NoError(t, err) + + assert.Equal(t, uint64(42), l) + + uni.hb.AssertExpectations(t) + + require.NoError(t, uni.configTracker.Close()) + }) +} diff --git a/core/services/relay/evm/contract_tracker.go b/core/services/relay/evm/contract_tracker.go deleted file mode 100644 index a0ecfcdf920..00000000000 --- a/core/services/relay/evm/contract_tracker.go +++ /dev/null @@ -1,447 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - gethCommon "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" - - evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" - httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" - "github.com/smartcontractkit/chainlink/core/chains/evm/log" - evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" - offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers2/generated/offchainaggregator" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/ocrcommon" - "github.com/smartcontractkit/chainlink/core/services/pg" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/sqlx" -) - -// configMailboxSanityLimit is the maximum number of configs that can be held -// in the mailbox. Under normal operation there should never be more than 0 or -// 1 configs in the mailbox, this limit is here merely to prevent unbounded usage -// in some kind of unforeseen insane situation. -const configMailboxSanityLimit = 100 - -var ( - _ ocrtypes.ContractConfigTracker = &ContractTracker{} - _ httypes.HeadTrackable = &ContractTracker{} - - OCRContractConfigSet = getEventTopic("ConfigSet") -) - -type OCRContractTrackerDB interface { - SaveLatestRoundRequested(tx pg.Queryer, rr ocr2aggregator.OCR2AggregatorRoundRequested) error - LoadLatestRoundRequested() (rr ocr2aggregator.OCR2AggregatorRoundRequested, err error) -} - -// ContractTracker complies with ContractConfigTracker interface and -// handles log events related to the contract more generally -//go:generate mockery --name OCRContractTrackerDB --output ./mocks/ --case=underscore -type ContractTracker struct { - utils.StartStopOnce - - ethClient evmclient.Client - contract *offchain_aggregator_wrapper.OffchainAggregator - contractFilterer *ocr2aggregator.OCR2AggregatorFilterer - contractCaller *ocr2aggregator.OCR2AggregatorCaller - logBroadcaster log.Broadcaster - jobID int32 - logger logger.Logger - odb OCRContractTrackerDB - q pg.Q - blockTranslator ocrcommon.BlockTranslator - chain ocrcommon.Config - - // HeadBroadcaster - headBroadcaster httypes.HeadBroadcaster - unsubscribeHeads func() - - // Start/Stop lifecycle - ctx context.Context - ctxCancel context.CancelFunc - wg sync.WaitGroup - unsubscribeLogs func() - - // LatestRoundRequested - latestRoundRequested ocr2aggregator.OCR2AggregatorRoundRequested - lrrMu sync.RWMutex - - // ContractConfig - configsMB utils.Mailbox - chConfigs chan ocrtypes.ContractConfig - - // LatestBlockHeight - latestBlockHeight int64 - latestBlockHeightMu sync.RWMutex -} - -// NewOCRContractTracker makes a new ContractTracker -func NewOCRContractTracker( - contract *offchain_aggregator_wrapper.OffchainAggregator, - contractFilterer *ocr2aggregator.OCR2AggregatorFilterer, - contractCaller *ocr2aggregator.OCR2AggregatorCaller, - ethClient evmclient.Client, - logBroadcaster log.Broadcaster, - jobID int32, - logger logger.Logger, - db *sqlx.DB, - odb OCRContractTrackerDB, - chain ocrcommon.Config, - headBroadcaster httypes.HeadBroadcaster, -) (o *ContractTracker) { - ctx, cancel := context.WithCancel(context.Background()) - return &ContractTracker{ - utils.StartStopOnce{}, - ethClient, - contract, - contractFilterer, - contractCaller, - logBroadcaster, - jobID, - logger, - odb, - pg.NewQ(db, logger, chain), - ocrcommon.NewBlockTranslator(chain, ethClient, logger), - chain, - headBroadcaster, - nil, - ctx, - cancel, - sync.WaitGroup{}, - nil, - ocr2aggregator.OCR2AggregatorRoundRequested{}, - sync.RWMutex{}, - *utils.NewMailbox(configMailboxSanityLimit), - make(chan ocrtypes.ContractConfig), - -1, - sync.RWMutex{}, - } -} - -// Start must be called before logs can be delivered -// It ought to be called before starting OCR -func (t *ContractTracker) Start() error { - return t.StartOnce("ContractTracker", func() (err error) { - t.latestRoundRequested, err = t.odb.LoadLatestRoundRequested() - if err != nil { - return errors.Wrap(err, "ContractTracker#Start: failed to load latest round requested") - } - - t.unsubscribeLogs = t.logBroadcaster.Register(t, log.ListenerOpts{ - Contract: t.contract.Address(), - ParseLog: t.contract.ParseLog, - LogsWithTopics: map[gethCommon.Hash][][]log.Topic{ - offchain_aggregator_wrapper.OffchainAggregatorRoundRequested{}.Topic(): nil, - offchain_aggregator_wrapper.OffchainAggregatorConfigSet{}.Topic(): nil, - }, - MinIncomingConfirmations: 1, - }) - - var latestHead *evmtypes.Head - latestHead, t.unsubscribeHeads = t.headBroadcaster.Subscribe(t) - if latestHead != nil { - t.setLatestBlockHeight(*latestHead) - } - - t.wg.Add(1) - go t.processLogs() - return nil - }) -} - -// Close should be called after teardown of the OCR job relying on this tracker -func (t *ContractTracker) Close() error { - return t.StopOnce("ContractTracker", func() error { - t.ctxCancel() - t.wg.Wait() - t.unsubscribeHeads() - t.unsubscribeLogs() - close(t.chConfigs) - return nil - }) -} - -// Connect conforms to HeadTrackable -func (t *ContractTracker) Connect(*evmtypes.Head) error { return nil } - -// OnNewLongestChain conformed to HeadTrackable and updates latestBlockHeight -func (t *ContractTracker) OnNewLongestChain(_ context.Context, h *evmtypes.Head) { - t.setLatestBlockHeight(*h) -} - -func (t *ContractTracker) setLatestBlockHeight(h evmtypes.Head) { - var num int64 - if h.L1BlockNumber.Valid { - num = h.L1BlockNumber.Int64 - } else { - num = h.Number - } - t.latestBlockHeightMu.Lock() - defer t.latestBlockHeightMu.Unlock() - if num > t.latestBlockHeight { - t.latestBlockHeight = num - } -} - -func (t *ContractTracker) getLatestBlockHeight() int64 { - t.latestBlockHeightMu.RLock() - defer t.latestBlockHeightMu.RUnlock() - return t.latestBlockHeight -} - -func (t *ContractTracker) processLogs() { - defer t.wg.Done() - for { - select { - case <-t.configsMB.Notify(): - // NOTE: libocr could take an arbitrary amount of time to process a - // new config. To avoid blocking the log broadcaster, we use this - // background thread to deliver them and a mailbox as the buffer. - for { - x, exists := t.configsMB.Retrieve() - if !exists { - break - } - cc, ok := x.(ocrtypes.ContractConfig) - if !ok { - panic(fmt.Sprintf("expected ocrtypes.ContractConfig but got %T", x)) - } - select { - case t.chConfigs <- cc: - case <-t.ctx.Done(): - return - } - } - case <-t.ctx.Done(): - return - } - } -} - -// HandleLog complies with LogListener interface -// It is not thread safe -func (t *ContractTracker) HandleLog(lb log.Broadcast) { - was, err := t.logBroadcaster.WasAlreadyConsumed(lb) - if err != nil { - t.logger.Errorw("OCRContract: could not determine if log was already consumed", "error", err) - return - } else if was { - return - } - - raw := lb.RawLog() - if raw.Address != t.contract.Address() { - t.logger.Errorf("log address of 0x%x does not match configured contract address of 0x%x", raw.Address, t.contract.Address()) - t.logger.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") - return - } - topics := raw.Topics - if len(topics) == 0 { - t.logger.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") - return - } - - var consumed bool - switch topics[0] { - case offchain_aggregator_wrapper.OffchainAggregatorConfigSet{}.Topic(): - var configSet *ocr2aggregator.OCR2AggregatorConfigSet - configSet, err = t.contractFilterer.ParseConfigSet(raw) - if err != nil { - t.logger.Errorw("could not parse config set", "err", err) - t.logger.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") - return - } - configSet.Raw = lb.RawLog() - cc := evmutil.ContractConfigFromConfigSetEvent(*configSet) - - wasOverCapacity := t.configsMB.Deliver(cc) - if wasOverCapacity { - t.logger.Error("config mailbox is over capacity - dropped the oldest unprocessed item") - } - case offchain_aggregator_wrapper.OffchainAggregatorRoundRequested{}.Topic(): - var rr *ocr2aggregator.OCR2AggregatorRoundRequested - rr, err = t.contractFilterer.ParseRoundRequested(raw) - if err != nil { - t.logger.Errorw("could not parse round requested", "err", err) - t.logger.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") - return - } - if IsLaterThan(raw, t.latestRoundRequested.Raw) { - err = t.q.Transaction(func(q pg.Queryer) error { - if err = t.odb.SaveLatestRoundRequested(q, *rr); err != nil { - return err - } - return t.logBroadcaster.MarkConsumed(lb, pg.WithQueryer(q)) - }) - if err != nil { - t.logger.Error(err) - return - } - consumed = true - t.lrrMu.Lock() - t.latestRoundRequested = *rr - t.lrrMu.Unlock() - t.logger.Infow("ContractTracker: received new latest RoundRequested event", "latestRoundRequested", *rr) - } else { - t.logger.Warnw("ContractTracker: ignoring out of date RoundRequested event", "latestRoundRequested", t.latestRoundRequested, "roundRequested", rr) - } - default: - t.logger.Debugw("ContractTracker: got unrecognised log topic", "topic", topics[0]) - } - if !consumed { - t.logger.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") - } -} - -// IsLaterThan returns true if the first log was emitted "after" the second log -// from the blockchain's point of view -func IsLaterThan(incoming gethTypes.Log, existing gethTypes.Log) bool { - return incoming.BlockNumber > existing.BlockNumber || - (incoming.BlockNumber == existing.BlockNumber && incoming.TxIndex > existing.TxIndex) || - (incoming.BlockNumber == existing.BlockNumber && incoming.TxIndex == existing.TxIndex && incoming.Index > existing.Index) -} - -// IsV2Job complies with LogListener interface -func (t *ContractTracker) IsV2Job() bool { - return true -} - -// JobID complies with LogListener interface -func (t *ContractTracker) JobID() int32 { - return t.jobID -} - -// Notify returns a channel that can wake up the contract tracker to let it -// know when a new config is available -func (t *ContractTracker) Notify() <-chan struct{} { - return nil -} - -// LatestConfigDetails queries the eth node -func (t *ContractTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { - var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) - defer cancel() - - opts := bind.CallOpts{Context: ctx, Pending: false} - result, err := t.contract.LatestConfigDetails(&opts) - if err != nil { - return 0, configDigest, errors.Wrap(err, "error getting LatestConfigDetails") - } - configDigest, err = ocrtypes.BytesToConfigDigest(result.ConfigDigest[:]) - if err != nil { - return 0, configDigest, errors.Wrap(err, "error getting config digest") - } - return uint64(result.BlockNumber), configDigest, err -} - -// Return the latest configuration -func (t *ContractTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (ocrtypes.ContractConfig, error) { - fromBlock, toBlock := t.blockTranslator.NumberToQueryRange(ctx, changedInBlock) - q := ethereum.FilterQuery{ - FromBlock: fromBlock, - ToBlock: toBlock, - Addresses: []gethCommon.Address{t.contract.Address()}, - Topics: [][]gethCommon.Hash{ - {OCRContractConfigSet}, - }, - } - - var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) - defer cancel() - - logs, err := t.ethClient.FilterLogs(ctx, q) - if err != nil { - return ocrtypes.ContractConfig{}, err - } - if len(logs) == 0 { - return ocrtypes.ContractConfig{}, errors.Errorf("ConfigFromLogs: OCRContract with address 0x%x has no logs", t.contract.Address()) - } - - latest, err := t.contractFilterer.ParseConfigSet(logs[len(logs)-1]) - if err != nil { - return ocrtypes.ContractConfig{}, errors.Wrap(err, "ConfigFromLogs failed to ParseConfigSet") - } - latest.Raw = logs[len(logs)-1] - if latest.Raw.Address != t.contract.Address() { - return ocrtypes.ContractConfig{}, errors.Errorf("log address of 0x%x does not match configured contract address of 0x%x", latest.Raw.Address, t.contract.Address()) - } - return evmutil.ContractConfigFromConfigSetEvent(*latest), err -} - -// LatestBlockHeight queries the eth node for the most recent header -func (t *ContractTracker) LatestBlockHeight(ctx context.Context) (blockheight uint64, err error) { - // We skip confirmation checking anyway on Optimism so there's no need to - // care about the block height; we have no way of getting the L1 block - // height anyway - if t.chain.ChainType() != "" { - return 0, nil - } - latestBlockHeight := t.getLatestBlockHeight() - if latestBlockHeight >= 0 { - return uint64(latestBlockHeight), nil - } - - t.logger.Debugw("ContractTracker: still waiting for first head, falling back to on-chain lookup") - - var cancel context.CancelFunc - ctx, cancel = utils.CombinedContext(t.ctx, ctx) - defer cancel() - - h, err := t.ethClient.HeadByNumber(ctx, nil) - if err != nil { - return 0, err - } - if h == nil { - return 0, errors.New("got nil head") - } - - if h.L1BlockNumber.Valid { - return uint64(h.L1BlockNumber.Int64), nil - } - - return uint64(h.Number), nil -} - -// LatestRoundRequested returns the configDigest, epoch, and round from the latest -// RoundRequested event emitted by the contract. LatestRoundRequested may or may not -// return a result if the latest such event was emitted in a block b such that -// b.timestamp < tip.timestamp - lookback. -// -// If no event is found, LatestRoundRequested should return zero values, not an error. -// An error should only be returned if an actual error occurred during execution, -// e.g. because there was an error querying the blockchain or the database. -// -// As an optimization, this function may also return zero values, if no -// RoundRequested event has been emitted after the latest NewTransmission event. -func (t *ContractTracker) LatestRoundRequested(_ context.Context, lookback time.Duration) (configDigest ocrtypes.ConfigDigest, epoch uint32, round uint8, err error) { - t.lrrMu.RLock() - defer t.lrrMu.RUnlock() - return t.latestRoundRequested.ConfigDigest, t.latestRoundRequested.Epoch, t.latestRoundRequested.Round, nil -} - -func getEventTopic(name string) gethCommon.Hash { - abi, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) - if err != nil { - panic("could not parse OffchainAggregator ABI: " + err.Error()) - } - event, exists := abi.Events[name] - if !exists { - panic(fmt.Sprintf("abi.Events was missing %s", name)) - } - return event.ID -} diff --git a/core/services/relay/evm/contract_transmitter.go b/core/services/relay/evm/contract_transmitter.go index e55ef89c747..a80890aab7a 100644 --- a/core/services/relay/evm/contract_transmitter.go +++ b/core/services/relay/evm/contract_transmitter.go @@ -4,57 +4,50 @@ import ( "context" "encoding/hex" "math/big" - "time" - - "github.com/smartcontractkit/chainlink/core/logger" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - gethCommon "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" -) -var ( - _ ocrtypes.ContractTransmitter = &ContractTransmitter{} - _ median.MedianContract = &ContractTransmitter{} + "github.com/smartcontractkit/chainlink/core/logger" ) +var _ ocrtypes.ContractTransmitter = &ContractTransmitter{} + type Transmitter interface { - CreateEthTransaction(ctx context.Context, toAddress gethCommon.Address, payload []byte) error - FromAddress() gethCommon.Address + CreateEthTransaction(ctx context.Context, toAddress gethcommon.Address, payload []byte) error + FromAddress() gethcommon.Address } type ContractTransmitter struct { - contractAddress gethCommon.Address + contractAddress gethcommon.Address contractABI abi.ABI transmitter Transmitter - contractCaller *ocr2aggregator.OCR2AggregatorCaller - tracker *ContractTracker + contractReader contractReader lggr logger.Logger } func NewOCRContractTransmitter( - address gethCommon.Address, - contractCaller *ocr2aggregator.OCR2AggregatorCaller, + address gethcommon.Address, + caller contractReader, contractABI abi.ABI, transmitter Transmitter, - tracker *ContractTracker, lggr logger.Logger, ) *ContractTransmitter { return &ContractTransmitter{ contractAddress: address, contractABI: contractABI, transmitter: transmitter, - contractCaller: contractCaller, - tracker: tracker, + contractReader: caller, lggr: lggr, } } +// Transmit sends the report to the on-chain smart contract's Transmit method. func (oc *ContractTransmitter) Transmit(ctx context.Context, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signatures []ocrtypes.AttributedOnchainSignature) error { var rs [][32]byte var ss [][32]byte @@ -80,33 +73,81 @@ func (oc *ContractTransmitter) Transmit(ctx context.Context, reportCtx ocrtypes. return errors.Wrap(oc.transmitter.CreateEthTransaction(ctx, oc.contractAddress, payload), "failed to send Eth transaction") } -func (oc *ContractTransmitter) LatestConfigDigestAndEpoch(ctx context.Context) (ocrtypes.ConfigDigest, uint32, error) { - opts := bind.CallOpts{Context: ctx, Pending: false} - result, err := oc.contractCaller.LatestTransmissionDetails(&opts) - return result.ConfigDigest, result.Epoch, errors.Wrap(err, "error getting LatestTransmissionDetails") +type contractReader interface { + CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) } -func (oc *ContractTransmitter) FromAccount() ocrtypes.Account { - return ocrtypes.Account(oc.transmitter.FromAddress().String()) +func parseTransmitted(log []byte) ([32]byte, uint32, error) { + mustType := func(ts string) abi.Type { + ty, _ := abi.NewType(ts, "", nil) + return ty + } + var args abi.Arguments = []abi.Argument{ + { + Name: "configDigest", + Type: mustType("bytes32"), + }, + { + Name: "epoch", + Type: mustType("uint32"), + }, + } + transmitted, err := args.Unpack(log) + if err != nil { + return [32]byte{}, 0, err + } + configDigest := *abi.ConvertType(transmitted[0], new([32]byte)).(*[32]byte) + epoch := *abi.ConvertType(transmitted[1], new(uint32)).(*uint32) + return configDigest, epoch, err } -func (oc *ContractTransmitter) LatestTransmissionDetails(ctx context.Context) (ocrtypes.ConfigDigest, uint32, uint8, *big.Int, time.Time, error) { - opts := bind.CallOpts{Context: ctx, Pending: false} - result, err := oc.contractCaller.LatestTransmissionDetails(&opts) - return result.ConfigDigest, result.Epoch, result.Round, result.LatestAnswer, time.Unix(int64(result.LatestTimestamp), 0), errors.Wrap(err, "error getting LatestTransmissionDetails") +// LatestConfigDigestAndEpoch retrieves the latest config digest and epoch from the OCR2 contract. +// It is plugin independent, in particular avoids use of the plugin specific generated evm wrappers +// by using the evm client Call directly for functions/events that are part of OCR2Abstract. +func (oc *ContractTransmitter) LatestConfigDigestAndEpoch(ctx context.Context) (ocrtypes.ConfigDigest, uint32, error) { + latestConfigDigestAndEpoch, err := callContract(ctx, oc.contractAddress, oc.contractABI, "latestConfigDigestAndEpoch", nil, oc.contractReader) + if err != nil { + return ocrtypes.ConfigDigest{}, 0, err + } + // Panic on these conversions erroring, would mean a broken contract. + scanLogs := *abi.ConvertType(latestConfigDigestAndEpoch[0], new(bool)).(*bool) + configDigest := *abi.ConvertType(latestConfigDigestAndEpoch[1], new([32]byte)).(*[32]byte) + epoch := *abi.ConvertType(latestConfigDigestAndEpoch[2], new(uint32)).(*uint32) + if !scanLogs { + return configDigest, epoch, nil + } + + // Otherwise, we have to scan for the logs. First get the latest config block as a log lower bound. + latestConfigDetails, err := callContract(ctx, oc.contractAddress, oc.contractABI, "latestConfigDetails", nil, oc.contractReader) + if err != nil { + return ocrtypes.ConfigDigest{}, 0, err + } + configBlock := *abi.ConvertType(latestConfigDetails[1], new(uint32)).(*uint32) + configDigest = *abi.ConvertType(latestConfigDetails[2], new([32]byte)).(*[32]byte) + topics, err := abi.MakeTopics([]interface{}{oc.contractABI.Events["Transmitted"].ID}) + if err != nil { + return ocrtypes.ConfigDigest{}, 0, err + } + query := ethereum.FilterQuery{ + Addresses: []gethcommon.Address{oc.contractAddress}, + Topics: topics, + FromBlock: new(big.Int).SetUint64(uint64(configBlock)), + } + logs, err := oc.contractReader.FilterLogs(ctx, query) + if err != nil { + return ocrtypes.ConfigDigest{}, 0, err + } + // No transmissions yet + if len(logs) == 0 { + return configDigest, 0, nil + } + // Logs come back ordered https://github.com/ethereum/go-ethereum/blob/d78590560d0107e727a44d0eea088eeb4d280bab/eth/filters/filter.go#L215 + // If there is a transmission, we take the latest one + return parseTransmitted(logs[len(logs)-1].Data) } -// LatestRoundRequested returns the configDigest, epoch, and round from the latest -// RoundRequested event emitted by the contract. LatestRoundRequested may or may not -// return a result if the latest such event was emitted in a block b such that -// b.timestamp < tip.timestamp - lookback. -// -// If no event is found, LatestRoundRequested should return zero values, not an error. -// An error should only be returned if an actual error occurred during execution, -// e.g. because there was an error querying the blockchain or the database. -// -// As an optimization, this function may also return zero values, if no -// RoundRequested event has been emitted after the latest NewTransmission event. -func (oc *ContractTransmitter) LatestRoundRequested(ctx context.Context, lookback time.Duration) (ocrtypes.ConfigDigest, uint32, uint8, error) { - return oc.tracker.LatestRoundRequested(ctx, lookback) +// FromAccount returns the account from which the transmitter invokes the contract +func (oc *ContractTransmitter) FromAccount() ocrtypes.Account { + return ocrtypes.Account(oc.transmitter.FromAddress().String()) } diff --git a/core/services/relay/evm/contract_transmitter_test.go b/core/services/relay/evm/contract_transmitter_test.go new file mode 100644 index 00000000000..1a8fc01253d --- /dev/null +++ b/core/services/relay/evm/contract_transmitter_test.go @@ -0,0 +1,71 @@ +package evm + +import ( + "context" + "encoding/hex" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum/go-ethereum/accounts/abi" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/logger" +) + +func TestContractTransmitter(t *testing.T) { + lggr := logger.TestLogger(t) + c := new(evmmocks.Client) + // scanLogs = false + digestAndEpochDontScanLogs, _ := hex.DecodeString( + "0000000000000000000000000000000000000000000000000000000000000000" + // false + "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776" + // config digest + "0000000000000000000000000000000000000000000000000000000000000002") // epoch + c.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(digestAndEpochDontScanLogs, nil).Once() + contractABI, _ := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) + ot := NewOCRContractTransmitter(gethcommon.Address{}, c, contractABI, nil, lggr) + digest, epoch, err := ot.LatestConfigDigestAndEpoch(context.Background()) + require.NoError(t, err) + assert.Equal(t, "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776", hex.EncodeToString(digest[:])) + assert.Equal(t, uint32(2), epoch) + + // scanLogs = true + digestAndEpochScanLogs, _ := hex.DecodeString( + "0000000000000000000000000000000000000000000000000000000000000001" + // false + "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776" + // config digest + "0000000000000000000000000000000000000000000000000000000000000002") // epoch + c.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(digestAndEpochScanLogs, nil).Once() + // We expect it will call for latest config details to get a lower bound on the log search. + latestConfigDetails, _ := hex.DecodeString( + "0000000000000000000000000000000000000000000000000000000000000001" + // config count + "0000000000000000000000000000000000000000000000000000000000000002" + // block num + "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776") // digest + c.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(latestConfigDetails, nil) + + transmitted1, _ := hex.DecodeString( + "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776" + // config digest + "0000000000000000000000000000000000000000000000000000000000000001") // epoch + transmitted2, _ := hex.DecodeString( + "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc777" + // config digest + "0000000000000000000000000000000000000000000000000000000000000002") // epoch + c.On("FilterLogs", mock.Anything, mock.Anything).Return( + []types.Log{ + { + Data: transmitted1, + }, + { + Data: transmitted2, + }, + }, nil) + digest, epoch, err = ot.LatestConfigDigestAndEpoch(context.Background()) + require.NoError(t, err) + assert.Equal(t, "000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc777", hex.EncodeToString(digest[:])) + assert.Equal(t, uint32(2), epoch) + c.AssertExpectations(t) +} diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 1cd12bd442c..bb52ad307d0 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -1,30 +1,31 @@ package evm import ( + "context" "math/big" "strings" - types2 "github.com/smartcontractkit/chainlink/core/services/relay/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" - txm "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers2/generated/offchainaggregator" - "github.com/smartcontractkit/chainlink/core/services/ocrcommon" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median/evmreportcodec" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/sqlx" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + "github.com/smartcontractkit/chainlink/core/chains/evm" + txm "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" + types2 "github.com/smartcontractkit/chainlink/core/services/relay/types" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" - "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/sqlx" ) type Relayer struct { @@ -42,7 +43,7 @@ func NewRelayer(db *sqlx.DB, chainSet evm.ChainSet, lggr logger.Logger) *Relayer } // Start does noop: no subservices started on relay start, but when the first job is started -func (r *Relayer) Start() error { +func (r *Relayer) Start(context.Context) error { return nil } @@ -61,7 +62,9 @@ func (r *Relayer) Healthy() error { return nil } -func (r *Relayer) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types2.OCR2Provider, error) { +// NewOCR2Provider provides all evm specific implementations of OCR2 components +// including components generic across all plugins and ones specific to plugins. +func (r *Relayer) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types2.OCR2ProviderCtx, error) { // Expect trusted input spec := s.(OCR2Spec) chain, err := r.chainSet.Get(spec.ChainID) @@ -72,37 +75,15 @@ func (r *Relayer) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types return nil, errors.Errorf("invalid contractID, expected hex address") } contractAddress := common.HexToAddress(spec.ContractID) - - contract, err := offchain_aggregator_wrapper.NewOffchainAggregator(contractAddress, chain.Client()) - if err != nil { - return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregator") - } - - contractFilterer, err := ocr2aggregator.NewOCR2AggregatorFilterer(contractAddress, chain.Client()) - if err != nil { - return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregatorFilterer") - } - - contractCaller, err := ocr2aggregator.NewOCR2AggregatorCaller(contractAddress, chain.Client()) + contractABI, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) if err != nil { - return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregatorCaller") + return nil, errors.Wrap(err, "could not get contract ABI JSON") } - - ocrDB := NewContractDB(r.db.DB, spec.ID, r.lggr) - - tracker := NewOCRContractTracker( - contract, - contractFilterer, - contractCaller, + configTracker := NewConfigTracker(r.lggr, contractABI, chain.Client(), - chain.LogBroadcaster(), - spec.ID, - r.lggr, - r.db, - ocrDB, - chain.Config(), - chain.HeadBroadcaster(), - ) + contractAddress, + chain.Config().ChainType(), + chain.HeadBroadcaster()) offchainConfigDigester := evmutil.EVMOffchainConfigDigester{ ChainID: chain.Config().ChainID().Uint64(), @@ -112,18 +93,12 @@ func (r *Relayer) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types if spec.IsBootstrap { // Return early if bootstrap node (doesn't require the full OCR2 provider) return &ocr2Provider{ - tracker: tracker, + tracker: configTracker, offchainConfigDigester: offchainConfigDigester, + plugin: spec.Plugin, }, nil } - reportCodec := evmreportcodec.ReportCodec{} - - contractABI, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) - if err != nil { - return nil, errors.Wrap(err, "could not get contract ABI JSON") - } - if !spec.TransmitterID.Valid { return nil, errors.New("transmitterID is required for non-bootstrap jobs") } @@ -131,19 +106,26 @@ func (r *Relayer) NewOCR2Provider(externalJobID uuid.UUID, s interface{}) (types strategy := txm.NewQueueingTxStrategy(externalJobID, chain.Config().OCRDefaultTransactionQueueDepth()) contractTransmitter := NewOCRContractTransmitter( - contract.Address(), - contractCaller, + contractAddress, + chain.Client(), contractABI, ocrcommon.NewTransmitter(chain.TxManager(), transmitterAddress, chain.Config().EvmGasLimitDefault(), strategy, txm.TransmitCheckerSpec{}), - tracker, r.lggr, ) + medianContract, err := newMedianContract(contractAddress, chain, spec.ID, r.db, r.lggr) + if err != nil { + return nil, errors.Wrap(err, "error during median contract setup") + } + + reportCodec := evmreportcodec.ReportCodec{} + return &ocr2Provider{ - tracker: tracker, + tracker: configTracker, offchainConfigDigester: offchainConfigDigester, reportCodec: reportCodec, contractTransmitter: contractTransmitter, + medianContract: medianContract, }, nil } @@ -157,35 +139,56 @@ type OCR2Spec struct { TransmitterID null.String // Will be null for bootstrap jobs IsBootstrap bool ChainID *big.Int + Plugin job.OCR2PluginType } -var _ services.Service = (*ocr2Provider)(nil) +var _ services.ServiceCtx = (*ocr2Provider)(nil) type ocr2Provider struct { - tracker *ContractTracker + tracker *ConfigTracker offchainConfigDigester types.OffchainConfigDigester - reportCodec median.ReportCodec contractTransmitter *ContractTransmitter + plugin job.OCR2PluginType + // Median specific + reportCodec median.ReportCodec + medianContract *medianContract } -// On start, an ethereum ocr2 provider will start the contract tracker. -func (p ocr2Provider) Start() error { - return p.tracker.Start() +// Start an ethereum ocr2 provider will start the contract tracker. +func (p ocr2Provider) Start(context.Context) error { + err := p.tracker.Start() + if err != nil { + return err + } + // TODO (https://app.shortcut.com/chainlinklabs/story/32017/plugin-specific-relay-interfaces): + // We need to break up ocr2Provider into more granular components + // per plugin (would require changes in solana/terra relay repos) + if p.plugin == job.Median && p.medianContract != nil { + return p.medianContract.Start() + } + return nil } -// On close, an ethereum ocr2 provider will close the contract tracker. +// Close an ethereum ocr2 provider will close the contract tracker. func (p ocr2Provider) Close() error { - return p.tracker.Close() + err := p.tracker.Close() + if err != nil { + return err + } + if p.plugin == job.Median && p.medianContract != nil { + return p.medianContract.Close() + } + return nil } -// An ethereum ocr2 provider is ready if the contract tracker is ready. +// Ready always returns ready. func (p ocr2Provider) Ready() error { - return p.tracker.Ready() + return nil } -// An ethereum ocr2 provider is healthy if the contract tracker is healthy. +// Healthy always returns healthy. func (p ocr2Provider) Healthy() error { - return p.tracker.Healthy() + return nil } func (p ocr2Provider) ContractTransmitter() types.ContractTransmitter { @@ -205,5 +208,5 @@ func (p ocr2Provider) ReportCodec() median.ReportCodec { } func (p ocr2Provider) MedianContract() median.MedianContract { - return p.contractTransmitter + return p.medianContract } diff --git a/core/services/relay/evm/median.go b/core/services/relay/evm/median.go new file mode 100644 index 00000000000..9f3fecdc49b --- /dev/null +++ b/core/services/relay/evm/median.go @@ -0,0 +1,87 @@ +package evm + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/sqlx" + + "github.com/smartcontractkit/chainlink/core/chains/evm" + offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers2/generated/offchainaggregator" + "github.com/smartcontractkit/chainlink/core/logger" +) + +var _ median.MedianContract = &medianContract{} + +type medianContract struct { + contractCaller *ocr2aggregator.OCR2AggregatorCaller + tracker *RequestRoundTracker +} + +func newMedianContract(contractAddress common.Address, chain evm.Chain, specID int32, db *sqlx.DB, lggr logger.Logger) (*medianContract, error) { + contract, err := offchain_aggregator_wrapper.NewOffchainAggregator(contractAddress, chain.Client()) + if err != nil { + return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregator") + } + + contractFilterer, err := ocr2aggregator.NewOCR2AggregatorFilterer(contractAddress, chain.Client()) + if err != nil { + return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregatorFilterer") + } + + contractCaller, err := ocr2aggregator.NewOCR2AggregatorCaller(contractAddress, chain.Client()) + if err != nil { + return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregatorCaller") + } + + return &medianContract{ + contractCaller: contractCaller, + tracker: NewRequestRoundTracker( + contract, + contractFilterer, + chain.Client(), + chain.LogBroadcaster(), + specID, + lggr, + db, + NewRoundRequestedDB(db.DB, specID, lggr), + chain.Config(), + ), + }, nil +} + +func (oc *medianContract) Start() error { + return oc.tracker.Start() +} + +func (oc *medianContract) Close() error { + return oc.tracker.Close() +} + +func (oc *medianContract) LatestTransmissionDetails(ctx context.Context) (ocrtypes.ConfigDigest, uint32, uint8, *big.Int, time.Time, error) { + opts := bind.CallOpts{Context: ctx, Pending: false} + result, err := oc.contractCaller.LatestTransmissionDetails(&opts) + return result.ConfigDigest, result.Epoch, result.Round, result.LatestAnswer, time.Unix(int64(result.LatestTimestamp), 0), errors.Wrap(err, "error getting LatestTransmissionDetails") +} + +// LatestRoundRequested returns the configDigest, epoch, and round from the latest +// RoundRequested event emitted by the contract. LatestRoundRequested may or may not +// return a result if the latest such event was emitted in a block b such that +// b.timestamp < tip.timestamp - lookback. +// +// If no event is found, LatestRoundRequested should return zero values, not an error. +// An error should only be returned if an actual error occurred during execution, +// e.g. because there was an error querying the blockchain or the database. +// +// As an optimization, this function may also return zero values, if no +// RoundRequested event has been emitted after the latest NewTransmission event. +func (oc *medianContract) LatestRoundRequested(ctx context.Context, lookback time.Duration) (ocrtypes.ConfigDigest, uint32, uint8, error) { + return oc.tracker.LatestRoundRequested(ctx, lookback) +} diff --git a/core/services/relay/evm/contract_db.go b/core/services/relay/evm/request_round_db.go similarity index 62% rename from core/services/relay/evm/contract_db.go rename to core/services/relay/evm/request_round_db.go index 1fc8fdc99f8..3a8fbcd372a 100644 --- a/core/services/relay/evm/contract_db.go +++ b/core/services/relay/evm/request_round_db.go @@ -5,33 +5,42 @@ import ( "encoding/json" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/pg" ) -var _ OCRContractTrackerDB = &contractDB{} +//go:generate mockery --name OCRContractTrackerDB --output ./mocks/ --case=underscore + +// RequestRoundDB stores requested rounds for querying by the median plugin. +type RequestRoundDB interface { + SaveLatestRoundRequested(tx pg.Queryer, rr ocr2aggregator.OCR2AggregatorRoundRequested) error + LoadLatestRoundRequested() (rr ocr2aggregator.OCR2AggregatorRoundRequested, err error) +} + +var _ RequestRoundDB = &requestRoundDB{} -type contractDB struct { +type requestRoundDB struct { *sql.DB oracleSpecID int32 lggr logger.Logger } // NewDB returns a new DB scoped to this oracleSpecID -func NewContractDB(sqldb *sql.DB, oracleSpecID int32, lggr logger.Logger) *contractDB { - return &contractDB{sqldb, oracleSpecID, lggr} +func NewRoundRequestedDB(sqldb *sql.DB, oracleSpecID int32, lggr logger.Logger) *requestRoundDB { + return &requestRoundDB{sqldb, oracleSpecID, lggr} } -func (d *contractDB) SaveLatestRoundRequested(tx pg.Queryer, rr ocr2aggregator.OCR2AggregatorRoundRequested) error { +func (d *requestRoundDB) SaveLatestRoundRequested(tx pg.Queryer, rr ocr2aggregator.OCR2AggregatorRoundRequested) error { rawLog, err := json.Marshal(rr.Raw) if err != nil { return errors.Wrap(err, "could not marshal log as JSON") } _, err = tx.Exec(` -INSERT INTO offchainreporting2_latest_round_requested (offchainreporting2_oracle_spec_id, requester, config_digest, epoch, round, raw) -VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (offchainreporting2_oracle_spec_id) DO UPDATE SET +INSERT INTO ocr2_latest_round_requested (ocr2_oracle_spec_id, requester, config_digest, epoch, round, raw) +VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (ocr2_oracle_spec_id) DO UPDATE SET requester = EXCLUDED.requester, config_digest = EXCLUDED.config_digest, epoch = EXCLUDED.epoch, @@ -42,12 +51,12 @@ VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (offchainreporting2_oracle_spec_id) DO UP return errors.Wrap(err, "could not save latest round requested") } -func (d *contractDB) LoadLatestRoundRequested() (ocr2aggregator.OCR2AggregatorRoundRequested, error) { +func (d *requestRoundDB) LoadLatestRoundRequested() (ocr2aggregator.OCR2AggregatorRoundRequested, error) { rr := ocr2aggregator.OCR2AggregatorRoundRequested{} rows, err := d.Query(` SELECT requester, config_digest, epoch, round, raw -FROM offchainreporting2_latest_round_requested -WHERE offchainreporting2_oracle_spec_id = $1 +FROM ocr2_latest_round_requested +WHERE ocr2_oracle_spec_id = $1 LIMIT 1 `, d.oracleSpecID) if err != nil { diff --git a/core/services/relay/evm/contract_db_test.go b/core/services/relay/evm/request_round_db_test.go similarity index 90% rename from core/services/relay/evm/contract_db_test.go rename to core/services/relay/evm/request_round_db_test.go index c0836295abb..9da10e5d350 100644 --- a/core/services/relay/evm/contract_db_test.go +++ b/core/services/relay/evm/request_round_db_test.go @@ -3,17 +3,17 @@ package evm_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/services/relay/evm" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2/testhelpers" + "github.com/smartcontractkit/chainlink/core/services/ocr2/testhelpers" "github.com/smartcontractkit/chainlink/core/services/pg" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/core/services/relay/evm" ) func Test_DB_LatestRoundRequested(t *testing.T) { @@ -23,8 +23,8 @@ func Test_DB_LatestRoundRequested(t *testing.T) { require.NoError(t, err) lggr := logger.TestLogger(t) - db := evm.NewContractDB(sqlDB.DB, 1, lggr) - db2 := evm.NewContractDB(sqlDB.DB, 2, lggr) + db := evm.NewRoundRequestedDB(sqlDB.DB, 1, lggr) + db2 := evm.NewRoundRequestedDB(sqlDB.DB, 2, lggr) rawLog := cltest.LogFromFixture(t, "../../../testdata/jsonrpc/round_requested_log_1_1.json") @@ -72,7 +72,7 @@ func Test_DB_LatestRoundRequested(t *testing.T) { }) t.Run("spec with latest round requested can be deleted", func(t *testing.T) { - _, err := sqlDB.Exec(`DELETE FROM offchainreporting2_oracle_specs`) + _, err := sqlDB.Exec(`DELETE FROM ocr2_oracle_specs`) assert.NoError(t, err) }) } diff --git a/core/services/relay/evm/request_round_tracker.go b/core/services/relay/evm/request_round_tracker.go new file mode 100644 index 00000000000..da3cc56e1a5 --- /dev/null +++ b/core/services/relay/evm/request_round_tracker.go @@ -0,0 +1,204 @@ +package evm + +import ( + "context" + "sync" + "time" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/sqlx" + + evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/core/chains/evm/log" + offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers2/generated/offchainaggregator" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/core/services/pg" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// RequestRoundTracker subscribes to new request round logs. +type RequestRoundTracker struct { + utils.StartStopOnce + + ethClient evmclient.Client + contract *offchain_aggregator_wrapper.OffchainAggregator + contractFilterer *ocr2aggregator.OCR2AggregatorFilterer + logBroadcaster log.Broadcaster + jobID int32 + lggr logger.Logger + odb RequestRoundDB + q pg.Q + blockTranslator ocrcommon.BlockTranslator + + // Start/Stop lifecycle + ctx context.Context + ctxCancel context.CancelFunc + unsubscribeLogs func() + + // LatestRoundRequested + latestRoundRequested ocr2aggregator.OCR2AggregatorRoundRequested + lrrMu sync.RWMutex + + // LatestBlockHeight + latestBlockHeight int64 + latestBlockHeightMu sync.RWMutex +} + +// NewRequestRoundTracker makes a new RequestRoundTracker +func NewRequestRoundTracker( + contract *offchain_aggregator_wrapper.OffchainAggregator, + contractFilterer *ocr2aggregator.OCR2AggregatorFilterer, + ethClient evmclient.Client, + logBroadcaster log.Broadcaster, + jobID int32, + lggr logger.Logger, + db *sqlx.DB, + odb RequestRoundDB, + chain ocrcommon.Config, +) (o *RequestRoundTracker) { + ctx, cancel := context.WithCancel(context.Background()) + return &RequestRoundTracker{ + ethClient: ethClient, + contract: contract, + contractFilterer: contractFilterer, + logBroadcaster: logBroadcaster, + jobID: jobID, + lggr: lggr, + odb: odb, + q: pg.NewQ(db, lggr, chain), + blockTranslator: ocrcommon.NewBlockTranslator(chain, ethClient, lggr), + ctx: ctx, + ctxCancel: cancel, + latestBlockHeight: -1, + } +} + +// Start must be called before logs can be delivered +// It ought to be called before starting OCR +func (t *RequestRoundTracker) Start() error { + return t.StartOnce("RequestRoundTracker", func() (err error) { + t.latestRoundRequested, err = t.odb.LoadLatestRoundRequested() + if err != nil { + return errors.Wrap(err, "RequestRoundTracker#Start: failed to load latest round requested") + } + + t.unsubscribeLogs = t.logBroadcaster.Register(t, log.ListenerOpts{ + Contract: t.contract.Address(), + ParseLog: t.contract.ParseLog, + LogsWithTopics: map[gethCommon.Hash][][]log.Topic{ + offchain_aggregator_wrapper.OffchainAggregatorRoundRequested{}.Topic(): nil, + }, + MinIncomingConfirmations: 1, + }) + return nil + }) +} + +// Close should be called after teardown of the OCR job relying on this tracker +func (t *RequestRoundTracker) Close() error { + return t.StopOnce("RequestRoundTracker", func() error { + t.ctxCancel() + t.unsubscribeLogs() + return nil + }) +} + +// HandleLog complies with LogListener interface +// It is not thread safe +func (t *RequestRoundTracker) HandleLog(lb log.Broadcast) { + was, err := t.logBroadcaster.WasAlreadyConsumed(lb) + if err != nil { + t.lggr.Errorw("OCRContract: could not determine if log was already consumed", "error", err) + return + } else if was { + return + } + + raw := lb.RawLog() + if raw.Address != t.contract.Address() { + t.lggr.Errorf("log address of 0x%x does not match configured contract address of 0x%x", raw.Address, t.contract.Address()) + t.lggr.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") + return + } + topics := raw.Topics + if len(topics) == 0 { + t.lggr.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") + return + } + + var consumed bool + switch topics[0] { + case offchain_aggregator_wrapper.OffchainAggregatorRoundRequested{}.Topic(): + var rr *ocr2aggregator.OCR2AggregatorRoundRequested + rr, err = t.contractFilterer.ParseRoundRequested(raw) + if err != nil { + t.lggr.Errorw("could not parse round requested", "err", err) + t.lggr.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") + return + } + if IsLaterThan(raw, t.latestRoundRequested.Raw) { + err = t.q.Transaction(func(q pg.Queryer) error { + if err = t.odb.SaveLatestRoundRequested(q, *rr); err != nil { + return err + } + return t.logBroadcaster.MarkConsumed(lb, pg.WithQueryer(q)) + }) + if err != nil { + t.lggr.Error(err) + return + } + consumed = true + t.lrrMu.Lock() + t.latestRoundRequested = *rr + t.lrrMu.Unlock() + t.lggr.Infow("RequestRoundTracker: received new latest RoundRequested event", "latestRoundRequested", *rr) + } else { + t.lggr.Warnw("RequestRoundTracker: ignoring out of date RoundRequested event", "latestRoundRequested", t.latestRoundRequested, "roundRequested", rr) + } + default: + t.lggr.Debugw("RequestRoundTracker: got unrecognised log topic", "topic", topics[0]) + } + if !consumed { + t.lggr.ErrorIf(t.logBroadcaster.MarkConsumed(lb), "unable to mark consumed") + } +} + +// IsLaterThan returns true if the first log was emitted "after" the second log +// from the blockchain's point of view +func IsLaterThan(incoming gethTypes.Log, existing gethTypes.Log) bool { + return incoming.BlockNumber > existing.BlockNumber || + (incoming.BlockNumber == existing.BlockNumber && incoming.TxIndex > existing.TxIndex) || + (incoming.BlockNumber == existing.BlockNumber && incoming.TxIndex == existing.TxIndex && incoming.Index > existing.Index) +} + +// IsV2Job complies with LogListener interface +func (t *RequestRoundTracker) IsV2Job() bool { + return true +} + +// JobID complies with LogListener interface +func (t *RequestRoundTracker) JobID() int32 { + return t.jobID +} + +// LatestRoundRequested returns the configDigest, epoch, and round from the latest +// RoundRequested event emitted by the contract. LatestRoundRequested may or may not +// return a result if the latest such event was emitted in a block b such that +// b.timestamp < tip.timestamp - lookback. +// +// If no event is found, LatestRoundRequested should return zero values, not an error. +// An error should only be returned if an actual error occurred during execution, +// e.g. because there was an error querying the blockchain or the database. +// +// As an optimization, this function may also return zero values, if no +// RoundRequested event has been emitted after the latest NewTransmission event. +func (t *RequestRoundTracker) LatestRoundRequested(_ context.Context, lookback time.Duration) (configDigest ocrtypes.ConfigDigest, epoch uint32, round uint8, err error) { + t.lrrMu.RLock() + defer t.lrrMu.RUnlock() + return t.latestRoundRequested.ConfigDigest, t.latestRoundRequested.Epoch, t.latestRoundRequested.Round, nil +} diff --git a/core/services/relay/evm/contract_tracker_test.go b/core/services/relay/evm/request_round_tracker_test.go similarity index 70% rename from core/services/relay/evm/contract_tracker_test.go rename to core/services/relay/evm/request_round_tracker_test.go index a0f3b92b731..773ac17a472 100644 --- a/core/services/relay/evm/contract_tracker_test.go +++ b/core/services/relay/evm/request_round_tracker_test.go @@ -2,35 +2,34 @@ package evm_test import ( "context" - "math/big" + "strings" "testing" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2/testhelpers" - - offchainreporting "github.com/smartcontractkit/chainlink/core/services/relay/evm" - - evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" - evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/core/internal/testutils" - "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/core/services/relay/evm" + "github.com/ethereum/go-ethereum/accounts/abi" gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + evmconfig "github.com/smartcontractkit/chainlink/core/chains/evm/config" htmocks "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/mocks" logmocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" "github.com/smartcontractkit/chainlink/core/internal/cltest" offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/core/internal/gethwrappers2/generated/offchainaggregator" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" - ocrmocks "github.com/smartcontractkit/chainlink/core/services/offchainreporting2/mocks" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" + ocrmocks "github.com/smartcontractkit/chainlink/core/services/ocr2/mocks" + "github.com/smartcontractkit/chainlink/core/services/ocr2/testhelpers" ) func mustNewContract(t *testing.T, address gethCommon.Address) *offchain_aggregator_wrapper.OffchainAggregator { @@ -46,11 +45,12 @@ func mustNewFilterer(t *testing.T, address gethCommon.Address) *ocr2aggregator.O } type contractTrackerUni struct { - db *ocrmocks.OCRContractTrackerDB - lb *logmocks.Broadcaster - hb *htmocks.HeadBroadcaster - ec *evmmocks.Client - tracker *offchainreporting.ContractTracker + db *ocrmocks.OCRContractTrackerDB + lb *logmocks.Broadcaster + hb *htmocks.HeadBroadcaster + ec *evmmocks.Client + requestRoundTracker *evm.RequestRoundTracker + configTracker *evm.ConfigTracker } func newContractTrackerUni(t *testing.T, opts ...interface{}) (uni contractTrackerUni) { @@ -84,19 +84,21 @@ func newContractTrackerUni(t *testing.T, opts ...interface{}) (uni contractTrack uni.ec = new(evmmocks.Client) db := pgtest.NewSqlxDB(t) - uni.tracker = offchainreporting.NewOCRContractTracker( + lggr := logger.TestLogger(t) + uni.requestRoundTracker = evm.NewRequestRoundTracker( contract, filterer, - nil, uni.ec, uni.lb, 42, - logger.TestLogger(t), + lggr, db, uni.db, chain, - uni.hb, ) + contractABI, err := abi.JSON(strings.NewReader(offchain_aggregator_wrapper.OffchainAggregatorABI)) + require.NoError(t, err) + uni.configTracker = evm.NewConfigTracker(lggr, contractABI, uni.ec, contract.Address(), chain.ChainType(), uni.hb) t.Cleanup(func() { uni.db.AssertExpectations(t) @@ -108,73 +110,6 @@ func newContractTrackerUni(t *testing.T, opts ...interface{}) (uni contractTrack return uni } -func Test_OCRContractTracker_LatestBlockHeight(t *testing.T) { - t.Parallel() - - t.Run("on L2 chains, always returns 0", func(t *testing.T) { - uni := newContractTrackerUni(t, evmtest.ChainOptimismMainnet(t)) - l, err := uni.tracker.LatestBlockHeight(context.Background()) - require.NoError(t, err) - - assert.Equal(t, uint64(0), l) - }) - - t.Run("before first head incoming, looks up on-chain", func(t *testing.T) { - uni := newContractTrackerUni(t) - uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(&evmtypes.Head{Number: 42}, nil) - - l, err := uni.tracker.LatestBlockHeight(context.Background()) - require.NoError(t, err) - - assert.Equal(t, uint64(42), l) - }) - - t.Run("Before first head incoming, on client error returns error", func(t *testing.T) { - uni := newContractTrackerUni(t) - uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(nil, nil).Once() - - _, err := uni.tracker.LatestBlockHeight(context.Background()) - assert.EqualError(t, err, "got nil head") - - uni.ec.On("HeadByNumber", mock.AnythingOfType("*context.cancelCtx"), (*big.Int)(nil)).Return(nil, errors.New("bar")).Once() - - _, err = uni.tracker.LatestBlockHeight(context.Background()) - assert.EqualError(t, err, "bar") - - uni.ec.AssertExpectations(t) - }) - - t.Run("after first head incoming, uses cached value", func(t *testing.T) { - uni := newContractTrackerUni(t) - - uni.tracker.OnNewLongestChain(context.Background(), &evmtypes.Head{Number: 42}) - - l, err := uni.tracker.LatestBlockHeight(context.Background()) - require.NoError(t, err) - - assert.Equal(t, uint64(42), l) - }) - - t.Run("if headbroadcaster has it, uses the given value on start", func(t *testing.T) { - uni := newContractTrackerUni(t) - - uni.hb.On("Subscribe", uni.tracker).Return(&evmtypes.Head{Number: 42}, func() {}) - uni.db.On("LoadLatestRoundRequested").Return(ocr2aggregator.OCR2AggregatorRoundRequested{}, nil) - uni.lb.On("Register", uni.tracker, mock.Anything).Return(func() {}) - - require.NoError(t, uni.tracker.Start()) - - l, err := uni.tracker.LatestBlockHeight(context.Background()) - require.NoError(t, err) - - assert.Equal(t, uint64(42), l) - - uni.hb.AssertExpectations(t) - - require.NoError(t, uni.tracker.Close()) - }) -} - func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testing.T) { t.Parallel() @@ -191,15 +126,15 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin uni.lb.On("MarkConsumed", mock.Anything, mock.Anything).Return(nil) uni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(false, nil) - configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err := uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) require.Equal(t, 0, int(epoch)) - uni.tracker.HandleLog(logBroadcast) + uni.requestRoundTracker.HandleLog(logBroadcast) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) @@ -215,15 +150,15 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin uni.lb.On("WasAlreadyConsumed", mock.Anything, mock.Anything).Return(true, nil) - configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err := uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) require.Equal(t, 0, int(epoch)) - uni.tracker.HandleLog(logBroadcast) + uni.requestRoundTracker.HandleLog(logBroadcast) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) @@ -236,7 +171,7 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin t.Run("for new round requested log", func(t *testing.T) { uni := newContractTrackerUni(t, fixtureFilterer, fixtureContract) - configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err := uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) @@ -254,11 +189,11 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin return rr.Epoch == 1 && rr.Round == 1 })).Return(nil) - uni.tracker.HandleLog(logBroadcast) + uni.requestRoundTracker.HandleLog(logBroadcast) uni.db.AssertExpectations(t) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) assert.Equal(t, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", configDigest.Hex()) assert.Equal(t, 1, int(epoch)) @@ -275,11 +210,11 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin return rr.Epoch == 1 && rr.Round == 9 })).Return(nil) - uni.tracker.HandleLog(logBroadcast2) + uni.requestRoundTracker.HandleLog(logBroadcast2) uni.db.AssertExpectations(t) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) assert.Equal(t, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", configDigest.Hex()) assert.Equal(t, 1, int(epoch)) @@ -288,11 +223,11 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin logBroadcast.AssertExpectations(t) // Same round with lower epoch is ignored - uni.tracker.HandleLog(logBroadcast) + uni.requestRoundTracker.HandleLog(logBroadcast) uni.db.AssertExpectations(t) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) assert.Equal(t, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", configDigest.Hex()) assert.Equal(t, 1, int(epoch)) @@ -312,11 +247,11 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin return rr.Epoch == 2 && rr.Round == 1 })).Return(nil) - uni.tracker.HandleLog(logBroadcast3) + uni.requestRoundTracker.HandleLog(logBroadcast3) uni.db.AssertExpectations(t) - configDigest, epoch, round, err = uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err = uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) assert.Equal(t, "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", configDigest.Hex()) assert.Equal(t, 2, int(epoch)) @@ -337,11 +272,11 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin uni.db.On("SaveLatestRoundRequested", mock.Anything, mock.Anything).Return(errors.New("something exploded")) - uni.tracker.HandleLog(logBroadcast) + uni.requestRoundTracker.HandleLog(logBroadcast) uni.db.AssertExpectations(t) - configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err := uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) require.Equal(t, ocrtypes.ConfigDigest{}, configDigest) require.Equal(t, 0, int(round)) @@ -361,17 +296,14 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin } eventuallyCloseLogBroadcaster := cltest.NewAwaiter() - uni.lb.On("Register", uni.tracker, mock.Anything).Return(func() { eventuallyCloseLogBroadcaster.ItHappened() }) + uni.lb.On("Register", uni.requestRoundTracker, mock.Anything).Return(func() { eventuallyCloseLogBroadcaster.ItHappened() }) uni.lb.On("IsConnected").Return(true).Maybe() - eventuallyCloseHeadBroadcaster := cltest.NewAwaiter() - uni.hb.On("Subscribe", uni.tracker).Return(nil, func() { eventuallyCloseHeadBroadcaster.ItHappened() }) - uni.db.On("LoadLatestRoundRequested").Return(rr, nil) - require.NoError(t, uni.tracker.Start()) + require.NoError(t, uni.requestRoundTracker.Start()) - configDigest, epoch, round, err := uni.tracker.LatestRoundRequested(context.Background(), 0) + configDigest, epoch, round, err := uni.requestRoundTracker.LatestRoundRequested(context.Background(), 0) require.NoError(t, err) assert.Equal(t, (ocrtypes.ConfigDigest)(rr.ConfigDigest).Hex(), configDigest.Hex()) assert.Equal(t, rr.Epoch, epoch) @@ -381,9 +313,8 @@ func Test_OCRContractTracker_HandleLog_OCRContractLatestRoundRequested(t *testin uni.lb.AssertExpectations(t) uni.hb.AssertExpectations(t) - require.NoError(t, uni.tracker.Close()) + require.NoError(t, uni.requestRoundTracker.Close()) - eventuallyCloseHeadBroadcaster.AssertHappened(t, true) eventuallyCloseLogBroadcaster.AssertHappened(t, true) }) } @@ -443,7 +374,7 @@ func Test_OCRContractTracker_IsLaterThan(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - res := offchainreporting.IsLaterThan(test.incoming, test.existing) + res := evm.IsLaterThan(test.incoming, test.existing) assert.Equal(t, test.expected, res) }) } diff --git a/core/services/relay/types/relay.go b/core/services/relay/types/relay.go index 2e8638aa08a..6c2e9b3409a 100644 --- a/core/services/relay/types/relay.go +++ b/core/services/relay/types/relay.go @@ -3,10 +3,10 @@ package types import ( + uuid "github.com/satori/go.uuid" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2/types" - uuid "github.com/satori/go.uuid" "github.com/smartcontractkit/chainlink/core/services" ) @@ -18,16 +18,27 @@ var ( Terra Network = "terra" ) -type Relayer interface { - services.Service - NewOCR2Provider(externalJobID uuid.UUID, spec interface{}) (OCR2Provider, error) +// RelayerCtx represents a relayer +type RelayerCtx interface { + services.ServiceCtx + // NewOCR2Provider is generic for all OCR2 plugins on the given chain. + NewOCR2Provider(externalJobID uuid.UUID, spec interface{}) (OCR2ProviderCtx, error) + // TODO: Will need some CCIP plugin providers for chain specific implementations + // of request reading and tracking report status on dest chain. + // For now, the ocr2/plugins/ccip is EVM specific. } -type OCR2Provider interface { - services.Service +// OCR2ProviderCtx contains methods needed for job.OCR2OracleSpec functionality +type OCR2ProviderCtx interface { + services.ServiceCtx ContractTransmitter() types.ContractTransmitter ContractConfigTracker() types.ContractConfigTracker OffchainConfigDigester() types.OffchainConfigDigester + OCR2MedianProvider +} + +// OCR2MedianProvider contains methods needed for the median.Median plugin +type OCR2MedianProvider interface { ReportCodec() median.ReportCodec MedianContract() median.MedianContract } diff --git a/core/services/service.go b/core/services/service.go index 355b209cb7e..462998a2745 100644 --- a/core/services/service.go +++ b/core/services/service.go @@ -1,10 +1,11 @@ package services +import "context" + type ( - // Service represents a long running service inside the - // Application. + // Service represents a long-running service inside the Application. // - // Typically a Service will leverage utils.StartStopOnce to implement these + // Typically, a ServiceCtx will leverage utils.StartStopOnce to implement these // calls in a safe manner. // // Template @@ -14,7 +15,7 @@ type ( // type ( // // Expose a public interface so we can mock the service. // Foo interface { - // service.Service + // service.ServiceCtx // // // ... // } @@ -39,7 +40,7 @@ type ( // return f // } // - // func (f *foo) Start() error { + // func (f *foo) Start(ctx context.Context) error { // return f.StartOnce("Foo", func() error { // go f.run() // @@ -71,9 +72,13 @@ type ( // } // // } - Service interface { - // Start the service. - Start() error + + // ServiceCtx is a former Service interface, that changed Start function to receive a context. + // This is needed for services that make HTTP calls or DB queries in Start. + ServiceCtx interface { + // Start the service. Must quit immediately if the context is cancelled. + // The given context applies to Start function only and must not be retained. + Start(context.Context) error // Close stops the Service. // Invariants: Usually after this call the Service cannot be started // again, you need to build a new Service to do so. diff --git a/core/services/synchronization/explorer_client.go b/core/services/synchronization/explorer_client.go index 469002cd5ae..ae244c734a9 100644 --- a/core/services/synchronization/explorer_client.go +++ b/core/services/synchronization/explorer_client.go @@ -47,7 +47,7 @@ const ( // ExplorerClient encapsulates all the functionality needed to // push run information to explorer. type ExplorerClient interface { - services.Service + services.ServiceCtx Url() url.URL Status() ConnectionStatus Send(context.Context, []byte, ...int) @@ -56,13 +56,28 @@ type ExplorerClient interface { type NoopExplorerClient struct{} -func (NoopExplorerClient) Url() url.URL { return url.URL{} } -func (NoopExplorerClient) Status() ConnectionStatus { return ConnectionStatusDisconnected } -func (NoopExplorerClient) Start() error { return nil } -func (NoopExplorerClient) Close() error { return nil } -func (NoopExplorerClient) Healthy() error { return nil } -func (NoopExplorerClient) Ready() error { return nil } -func (NoopExplorerClient) Send(context.Context, []byte, ...int) {} +// Url always returns underlying url. +func (NoopExplorerClient) Url() url.URL { return url.URL{} } + +// Status always returns ConnectionStatusDisconnected. +func (NoopExplorerClient) Status() ConnectionStatus { return ConnectionStatusDisconnected } + +// Start is a no-op +func (NoopExplorerClient) Start(context.Context) error { return nil } + +// Close is a no-op +func (NoopExplorerClient) Close() error { return nil } + +// Healthy is a no-op +func (NoopExplorerClient) Healthy() error { return nil } + +// Ready is a no-op +func (NoopExplorerClient) Ready() error { return nil } + +// Send is a no-op +func (NoopExplorerClient) Send(context.Context, []byte, ...int) {} + +// Receive is a no-op func (NoopExplorerClient) Receive(context.Context, ...time.Duration) ([]byte, error) { return nil, nil } type explorerClient struct { @@ -116,7 +131,7 @@ func (ec *explorerClient) Status() ConnectionStatus { } // Start starts a write pump over a websocket. -func (ec *explorerClient) Start() error { +func (ec *explorerClient) Start(context.Context) error { return ec.StartOnce("Explorer client", func() error { ec.chStop = make(chan struct{}) ec.wg.Add(1) diff --git a/core/services/synchronization/explorer_client_test.go b/core/services/synchronization/explorer_client_test.go index 85e5584a6fd..1dffd1bffff 100644 --- a/core/services/synchronization/explorer_client_test.go +++ b/core/services/synchronization/explorer_client_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/synchronization" "github.com/smartcontractkit/chainlink/core/static" @@ -24,7 +25,7 @@ func TestWebSocketClient_ReconnectLoop(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) cltest.CallbackOrTimeout(t, "ws client connects", func() { <-wsserver.Connected }, 1*time.Second) @@ -48,7 +49,7 @@ func TestWebSocketClient_Authentication(t *testing.T) { url := cltest.MustParseURL(t, server.URL) url.Scheme = "ws" explorerClient := synchronization.NewExplorerClient(url, "accessKey", "secret", logger.TestLogger(t)) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() cltest.CallbackOrTimeout(t, "receive authentication headers", func() { @@ -65,7 +66,7 @@ func TestWebSocketClient_Send_DefaultsToTextMessage(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() expectation := `{"hello": "world"}` @@ -80,7 +81,7 @@ func TestWebSocketClient_Send_TextMessage(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() expectation := `{"hello": "world"}` @@ -95,7 +96,7 @@ func TestWebSocketClient_Send_Binary(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() address := common.HexToAddress("0xabc123") @@ -111,7 +112,7 @@ func TestWebSocketClient_Send_Unsupported(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) assert.PanicsWithValue(t, "send on explorer client received unsupported message type -1", func() { explorerClient.Send(context.Background(), []byte(`{"hello": "world"}`), -1) @@ -124,7 +125,7 @@ func TestWebSocketClient_Send_WithAck(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() expectation := `{"hello": "world"}` @@ -147,7 +148,7 @@ func TestWebSocketClient_Send_WithAckTimeout(t *testing.T) { defer cleanup() explorerClient := newTestExplorerClient(t, wsserver.URL) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() expectation := `{"hello": "world"}` @@ -170,7 +171,7 @@ func TestWebSocketClient_Status_ConnectAndServerDisconnect(t *testing.T) { explorerClient := newTestExplorerClient(t, wsserver.URL) assert.Equal(t, synchronization.ConnectionStatusDisconnected, explorerClient.Status()) - require.NoError(t, explorerClient.Start()) + require.NoError(t, explorerClient.Start(testutils.Context(t))) defer explorerClient.Close() cltest.CallbackOrTimeout(t, "ws client connects", func() { <-wsserver.Connected @@ -193,7 +194,7 @@ func TestWebSocketClient_Status_ConnectError(t *testing.T) { require.NoError(t, err) errorexplorerClient := newTestExplorerClient(t, badURL) - require.NoError(t, errorexplorerClient.Start()) + require.NoError(t, errorexplorerClient.Start(testutils.Context(t))) defer errorexplorerClient.Close() time.Sleep(100 * time.Millisecond) diff --git a/core/services/synchronization/helpers_test.go b/core/services/synchronization/helpers_test.go index 5fcbb1ccb68..e1df59874ab 100644 --- a/core/services/synchronization/helpers_test.go +++ b/core/services/synchronization/helpers_test.go @@ -18,8 +18,9 @@ func NewTestTelemetryIngressClient(t *testing.T, url *url.URL, serverPubKeyHex s } // NewTestTelemetryIngressBatchClient calls NewTelemetryIngressBatchClient and injects telemClient. -func NewTestTelemetryIngressBatchClient(t *testing.T, url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, telemClient telemPb.TelemClient, sendInterval time.Duration) TelemetryIngressBatchClient { - tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval) +func NewTestTelemetryIngressBatchClient(t *testing.T, url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, telemClient telemPb.TelemClient, sendInterval time.Duration, uniconn bool) TelemetryIngressBatchClient { + tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval, time.Second, uniconn) + tc.(*telemetryIngressBatchClient).close = func() error { return nil } tc.(*telemetryIngressBatchClient).telemClient = telemClient return tc } diff --git a/core/services/synchronization/telemetry_ingress_batch_client.go b/core/services/synchronization/telemetry_ingress_batch_client.go index b15c8e84ebc..04d3bc9b3f2 100644 --- a/core/services/synchronization/telemetry_ingress_batch_client.go +++ b/core/services/synchronization/telemetry_ingress_batch_client.go @@ -1,19 +1,22 @@ package synchronization import ( + "context" "errors" "fmt" "net/url" "sync" "time" + "github.com/smartcontractkit/wsrpc" + "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "go.uber.org/atomic" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/wsrpc" - "github.com/smartcontractkit/wsrpc/examples/simple/keys" ) //go:generate mockery --dir ./telem --name TelemClient --output ./mocks/ --case=underscore @@ -21,7 +24,7 @@ import ( // TelemetryIngressBatchClient encapsulates all the functionality needed to // send telemetry to the ingress server using wsrpc type TelemetryIngressBatchClient interface { - services.Service + services.ServiceCtx Send(TelemPayload) } @@ -29,7 +32,7 @@ type TelemetryIngressBatchClient interface { type NoopTelemetryIngressBatchClient struct{} // Start is a no-op -func (NoopTelemetryIngressBatchClient) Start() error { return nil } +func (NoopTelemetryIngressBatchClient) Start(context.Context) error { return nil } // Close is a no-op func (NoopTelemetryIngressBatchClient) Close() error { return nil } @@ -49,7 +52,10 @@ type telemetryIngressBatchClient struct { ks keystore.CSA serverPubKeyHex string - telemClient telemPb.TelemClient + connected *atomic.Bool + telemClient telemPb.TelemClient + close func() error + globalLogger logger.Logger logging bool lggr logger.Logger @@ -60,18 +66,22 @@ type telemetryIngressBatchClient struct { telemBufferSize uint telemMaxBatchSize uint telemSendInterval time.Duration + telemSendTimeout time.Duration workers map[string]*telemetryIngressBatchWorker workersMutex sync.Mutex + + useUniConn bool } // NewTelemetryIngressBatchClient returns a client backed by wsrpc that // can send telemetry to the telemetry ingress server -func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, telemMaxBatchSize uint, telemSendInterval time.Duration) TelemetryIngressBatchClient { +func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, telemMaxBatchSize uint, telemSendInterval time.Duration, telemSendTimeout time.Duration, useUniconn bool) TelemetryIngressBatchClient { return &telemetryIngressBatchClient{ telemBufferSize: telemBufferSize, telemMaxBatchSize: telemMaxBatchSize, telemSendInterval: telemSendInterval, + telemSendTimeout: telemSendTimeout, url: url, ks: ks, serverPubKeyHex: serverPubKeyHex, @@ -79,7 +89,9 @@ func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks key logging: logging, lggr: lggr.Named("TelemetryIngressBatchClient"), chDone: make(chan struct{}), + connected: atomic.NewBool(false), workers: make(map[string]*telemetryIngressBatchWorker), + useUniConn: useUniconn, } } @@ -89,7 +101,7 @@ func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks key // an error and wsrpc will continue to retry the connection. Eventually when the ingress // server does come back up, wsrpc will establish the connection without any interaction // on behalf of the node operator. -func (tc *telemetryIngressBatchClient) Start() error { +func (tc *telemetryIngressBatchClient) Start(ctx context.Context) error { return tc.StartOnce("TelemetryIngressBatchClient", func() error { clientPrivKey, err := tc.getCSAPrivateKey() if err != nil { @@ -98,25 +110,37 @@ func (tc *telemetryIngressBatchClient) Start() error { serverPubKey := keys.FromHex(tc.serverPubKeyHex) - conn, err := wsrpc.Dial(tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey)) - if err != nil { - return fmt.Errorf("Could not start TelemIngressBatchClient, Dial returned error: %v", err) - } - // Initialize a new wsrpc client caller // This is used to call RPC methods on the server if tc.telemClient == nil { // only preset for tests - tc.telemClient = telemPb.NewTelemClient(conn) + if tc.useUniConn { + go func() { + // Use background context to retry forever to connect + // Blocks until we connect + conn, err := wsrpc.DialUniWithContext(ctx, tc.lggr, tc.url.String(), clientPrivKey, serverPubKey) + if err != nil { + if ctx.Err() != nil { + tc.lggr.Warnw("gave up connecting to telemetry endpoint", "err", err) + } else { + tc.lggr.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err) + } + } else { + tc.telemClient = telemPb.NewTelemClient(conn) + tc.close = conn.Close + tc.connected.Store(true) + } + }() + } else { + // Spawns a goroutine that will eventually connect + conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey)) + if err != nil { + return fmt.Errorf("Could not start TelemIngressBatchClient, Dial returned error: %v", err) + } + tc.telemClient = telemPb.NewTelemClient(conn) + tc.close = func() error { conn.Close(); return nil } + } } - tc.wgDone.Add(1) - go func() { - // Wait for close - <-tc.chDone - conn.Close() - tc.wgDone.Done() - }() - return nil }) } @@ -126,6 +150,9 @@ func (tc *telemetryIngressBatchClient) Close() error { return tc.StopOnce("TelemetryIngressBatchClient", func() error { close(tc.chDone) tc.wgDone.Wait() + if (tc.useUniConn && tc.connected.Load()) || !tc.useUniConn { + return tc.close() + } return nil }) } @@ -147,6 +174,10 @@ func (tc *telemetryIngressBatchClient) getCSAPrivateKey() (privkey []byte, err e // the ingress server. If the worker telemetry buffer is full, messages are dropped // and a warning is logged. func (tc *telemetryIngressBatchClient) Send(payload TelemPayload) { + if tc.useUniConn && !tc.connected.Load() { + tc.lggr.Warnw("not connected to telemetry endpoint", "endpoint", tc.url.String()) + return + } worker := tc.findOrCreateWorker(payload) select { case worker.chTelemetry <- payload: @@ -169,6 +200,7 @@ func (tc *telemetryIngressBatchClient) findOrCreateWorker(payload TelemPayload) worker = NewTelemetryIngressBatchWorker( tc.telemMaxBatchSize, tc.telemSendInterval, + tc.telemSendTimeout, tc.telemClient, &tc.wgDone, tc.chDone, diff --git a/core/services/synchronization/telemetry_ingress_batch_client_test.go b/core/services/synchronization/telemetry_ingress_batch_client_test.go index f0fa89bc340..5bdf03fcee0 100644 --- a/core/services/synchronization/telemetry_ingress_batch_client_test.go +++ b/core/services/synchronization/telemetry_ingress_batch_client_test.go @@ -13,6 +13,7 @@ import ( "go.uber.org/atomic" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/csakey" ksmocks "github.com/smartcontractkit/chainlink/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/core/services/synchronization" @@ -36,8 +37,8 @@ func TestTelemetryIngressBatchClient_HappyPath(t *testing.T) { url := &url.URL{} serverPubKeyHex := "33333333333" sendInterval := time.Millisecond * 5 - telemIngressClient := synchronization.NewTestTelemetryIngressBatchClient(t, url, serverPubKeyHex, csaKeystore, false, telemClient, sendInterval) - require.NoError(t, telemIngressClient.Start()) + telemIngressClient := synchronization.NewTestTelemetryIngressBatchClient(t, url, serverPubKeyHex, csaKeystore, false, telemClient, sendInterval, false) + require.NoError(t, telemIngressClient.Start(testutils.Context(t))) // Create telemetry payloads for different contracts telemPayload1 := synchronization.TelemPayload{ diff --git a/core/services/synchronization/telemetry_ingress_batch_worker.go b/core/services/synchronization/telemetry_ingress_batch_worker.go index 67a5fb59554..7180be4308f 100644 --- a/core/services/synchronization/telemetry_ingress_batch_worker.go +++ b/core/services/synchronization/telemetry_ingress_batch_worker.go @@ -15,6 +15,7 @@ import ( type telemetryIngressBatchWorker struct { telemMaxBatchSize uint telemSendInterval time.Duration + telemSendTimeout time.Duration telemClient telemPb.TelemClient wgDone *sync.WaitGroup chDone chan struct{} @@ -30,6 +31,7 @@ type telemetryIngressBatchWorker struct { func NewTelemetryIngressBatchWorker( telemMaxBatchSize uint, telemSendInterval time.Duration, + telemSendTimeout time.Duration, telemClient telemPb.TelemClient, wgDone *sync.WaitGroup, chDone chan struct{}, @@ -40,6 +42,7 @@ func NewTelemetryIngressBatchWorker( ) *telemetryIngressBatchWorker { return &telemetryIngressBatchWorker{ telemSendInterval: telemSendInterval, + telemSendTimeout: telemSendTimeout, telemMaxBatchSize: telemMaxBatchSize, telemClient: telemClient, wgDone: wgDone, @@ -68,7 +71,9 @@ func (tw *telemetryIngressBatchWorker) Start() { // Send batched telemetry to the ingress server, log any errors telemBatchReq := tw.BuildTelemBatchReq() - _, err := tw.telemClient.TelemBatch(context.Background(), telemBatchReq) + ctx, cancel := context.WithTimeout(context.Background(), tw.telemSendTimeout) + _, err := tw.telemClient.TelemBatch(ctx, telemBatchReq) + cancel() if err != nil { tw.lggr.Warnf("Could not send telemetry: %v", err) diff --git a/core/services/synchronization/telemetry_ingress_batch_worker_test.go b/core/services/synchronization/telemetry_ingress_batch_worker_test.go index ebbc06f6be9..528f4f6f563 100644 --- a/core/services/synchronization/telemetry_ingress_batch_worker_test.go +++ b/core/services/synchronization/telemetry_ingress_batch_worker_test.go @@ -24,6 +24,7 @@ func TestTelemetryIngressWorker_BuildTelemBatchReq(t *testing.T) { worker := synchronization.NewTelemetryIngressBatchWorker( uint(maxTelemBatchSize), time.Millisecond*1, + time.Second, new(mocks.TelemClient), &sync.WaitGroup{}, make(chan struct{}), diff --git a/core/services/synchronization/telemetry_ingress_client.go b/core/services/synchronization/telemetry_ingress_client.go index 97160ea28d2..ab484ce9e12 100644 --- a/core/services/synchronization/telemetry_ingress_client.go +++ b/core/services/synchronization/telemetry_ingress_client.go @@ -8,13 +8,14 @@ import ( "go.uber.org/atomic" + "github.com/smartcontractkit/wsrpc" + "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/wsrpc" - "github.com/smartcontractkit/wsrpc/examples/simple/keys" ) //go:generate mockery --dir ./telem --name TelemClient --output ./mocks/ --case=underscore @@ -25,19 +26,26 @@ const SendIngressBufferSize = 100 // TelemetryIngressClient encapsulates all the functionality needed to // send telemetry to the ingress server using wsrpc type TelemetryIngressClient interface { - services.Service - Start() error - Close() error + services.ServiceCtx Send(TelemPayload) } type NoopTelemetryIngressClient struct{} -func (NoopTelemetryIngressClient) Start() error { return nil } -func (NoopTelemetryIngressClient) Close() error { return nil } +// Start is a no-op +func (NoopTelemetryIngressClient) Start(context.Context) error { return nil } + +// Close is a no-op +func (NoopTelemetryIngressClient) Close() error { return nil } + +// Send is a no-op func (NoopTelemetryIngressClient) Send(TelemPayload) {} -func (NoopTelemetryIngressClient) Healthy() error { return nil } -func (NoopTelemetryIngressClient) Ready() error { return nil } + +// Healthy is a no-op +func (NoopTelemetryIngressClient) Healthy() error { return nil } + +// Ready is a no-op +func (NoopTelemetryIngressClient) Ready() error { return nil } type telemetryIngressClient struct { utils.StartStopOnce @@ -76,14 +84,14 @@ func NewTelemetryIngressClient(url *url.URL, serverPubKeyHex string, ks keystore } // Start connects the wsrpc client to the telemetry ingress server -func (tc *telemetryIngressClient) Start() error { +func (tc *telemetryIngressClient) Start(ctx context.Context) error { return tc.StartOnce("TelemetryIngressClient", func() error { privkey, err := tc.getCSAPrivateKey() if err != nil { return err } - tc.connect(privkey) + tc.connect(ctx, privkey) return nil }) @@ -98,7 +106,7 @@ func (tc *telemetryIngressClient) Close() error { }) } -func (tc *telemetryIngressClient) connect(clientPrivKey []byte) { +func (tc *telemetryIngressClient) connect(ctx context.Context, clientPrivKey []byte) { tc.wgDone.Add(1) go func() { @@ -106,7 +114,7 @@ func (tc *telemetryIngressClient) connect(clientPrivKey []byte) { serverPubKey := keys.FromHex(tc.serverPubKeyHex) - conn, err := wsrpc.Dial(tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey)) + conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey)) if err != nil { tc.lggr.Errorf("Error connecting to telemetry ingress server: %v", err) return diff --git a/core/services/synchronization/telemetry_ingress_client_test.go b/core/services/synchronization/telemetry_ingress_client_test.go index d62f2098091..2334ab1f53f 100644 --- a/core/services/synchronization/telemetry_ingress_client_test.go +++ b/core/services/synchronization/telemetry_ingress_client_test.go @@ -13,6 +13,7 @@ import ( "go.uber.org/atomic" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/csakey" ksmocks "github.com/smartcontractkit/chainlink/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/core/services/synchronization" @@ -35,7 +36,7 @@ func TestTelemetryIngressClient_Send_HappyPath(t *testing.T) { url := &url.URL{} serverPubKeyHex := "33333333333" telemIngressClient := synchronization.NewTestTelemetryIngressClient(t, url, serverPubKeyHex, csaKeystore, false, telemClient) - require.NoError(t, telemIngressClient.Start()) + require.NoError(t, telemIngressClient.Start(testutils.Context(t))) defer telemIngressClient.Close() // Create the telemetry payload diff --git a/core/services/synchronization/uni_client_integration_test.go b/core/services/synchronization/uni_client_integration_test.go new file mode 100644 index 00000000000..49d32eace11 --- /dev/null +++ b/core/services/synchronization/uni_client_integration_test.go @@ -0,0 +1,40 @@ +package synchronization + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/smartcontractkit/wsrpc" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/synchronization/telem" +) + +func TestUniClient(t *testing.T) { + t.Skip() + privKey, err := hex.DecodeString("TODO") + require.NoError(t, err) + pubKey, err := hex.DecodeString("TODO") + require.NoError(t, err) + t.Log(len(privKey), len(pubKey)) + lggr := logger.TestLogger(t) + c, err := wsrpc.DialUniWithContext(context.Background(), + lggr, + "TODO", + privKey, + pubKey) + require.NoError(t, err) + t.Log(c) + client := telem.NewTelemClient(c) + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + resp, err := client.Telem(ctx, &telem.TelemRequest{ + Telemetry: []byte(`hello world`), + Address: "myaddress", + }) + cancel() + t.Log(resp, err) + require.NoError(t, c.Close()) +} diff --git a/core/services/versioning/orm.go b/core/services/versioning/orm.go index d0775d83e08..46a32bb315a 100644 --- a/core/services/versioning/orm.go +++ b/core/services/versioning/orm.go @@ -73,7 +73,8 @@ func CheckVersion(q pg.Queryer, lggr logger.Logger, appVersion string) (appv, db lggr.Debugw("No previous version set", "appVersion", appVersion) return nil, nil, nil } else if err != nil { - pqErr, ok := err.(*pgconn.PgError) + var pqErr *pgconn.PgError + ok := errors.As(err, &pqErr) if ok && pqErr.Code == "42P01" && pqErr.Message == `relation "node_versions" does not exist` { lggr.Debugw("Previous version not set; node_versions table does not exist", "appVersion", appVersion) return nil, nil, nil diff --git a/core/services/vrf/delegate.go b/core/services/vrf/delegate.go index 07efb8912f2..4bf65e97744 100644 --- a/core/services/vrf/delegate.go +++ b/core/services/vrf/delegate.go @@ -10,6 +10,8 @@ import ( "github.com/pkg/errors" "github.com/theodesp/go-heaps/pairing" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/chains/evm" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/aggregator_v3_interface" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface" @@ -20,7 +22,6 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/sqlx" ) type Delegate struct { @@ -70,7 +71,8 @@ func (d *Delegate) JobType() job.Type { func (d *Delegate) AfterJobCreated(spec job.Job) {} func (d *Delegate) BeforeJobDeleted(spec job.Job) {} -func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { +// ServicesForSpec satisfies the job.Delegate interface. +func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if jb.VRFSpec == nil || jb.PipelineSpec == nil { return nil, errors.Errorf("vrf.Delegate expects a VRFSpec and PipelineSpec to be present, got %+v", jb) } @@ -108,7 +110,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { if err != nil { return nil, err } - return []job.Service{&listenerV2{ + return []job.ServiceCtx{&listenerV2{ cfg: chain.Config(), l: lV2, ethClient: chain.Client(), @@ -127,10 +129,11 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { reqAdded: func() {}, headBroadcaster: chain.HeadBroadcaster(), wg: &sync.WaitGroup{}, + deduper: newLogDeduper(int(chain.Config().EvmFinalityDepth())), }}, nil } if _, ok := task.(*pipeline.VRFTask); ok { - return []job.Service{&listenerV1{ + return []job.ServiceCtx{&listenerV1{ cfg: chain.Config(), l: lV1, headBroadcaster: chain.HeadBroadcaster(), @@ -150,6 +153,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.Service, error) { respCount: GetStartingResponseCountsV1(d.q, lV1, chain.Client().ChainID().Uint64(), chain.Config().EvmFinalityDepth()), blockNumberToReqID: pairing.New(), reqAdded: func() {}, + deduper: newLogDeduper(int(chain.Config().EvmFinalityDepth())), }}, nil } } diff --git a/core/services/vrf/delegate_test.go b/core/services/vrf/delegate_test.go index 53107efaef8..b245bfc6bdc 100644 --- a/core/services/vrf/delegate_test.go +++ b/core/services/vrf/delegate_test.go @@ -8,13 +8,13 @@ import ( "time" "github.com/smartcontractkit/chainlink/core/chains/evm" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bptxmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" log_mocks "github.com/smartcontractkit/chainlink/core/chains/evm/log/mocks" eth_mocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" @@ -48,7 +48,7 @@ type vrfUniverse struct { ks keystore.Master vrfkey vrfkey.KeyV2 submitter common.Address - txm *bptxmmocks.TxManager + txm *txmmocks.TxManager hb httypes.HeadBroadcaster cc evm.ChainSet cid big.Int @@ -67,7 +67,7 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg *configtest.TestGeneralConfig) v // Don't mock db interactions prm := pipeline.NewORM(db, lggr, cfg) - txm := new(bptxmmocks.TxManager) + txm := new(txmmocks.TxManager) ks := keystore.New(db, utils.FastScryptParams, lggr, cfg) cc := evmtest.NewChainSet(t, evmtest.TestChainOpts{LogBroadcaster: lb, KeyStore: ks.Eth(), Client: ec, DB: db, GeneralConfig: cfg, TxManager: txm}) jrm := job.NewORM(db, cc, prm, ks, lggr, cfg) @@ -320,6 +320,7 @@ func TestDelegate_ValidLog(t *testing.T) { TxHash: txHash, BlockNumber: 10, BlockHash: bh, + Index: 1, }, }, { @@ -340,6 +341,7 @@ func TestDelegate_ValidLog(t *testing.T) { TxHash: txHash, BlockNumber: 10, BlockHash: bh, + Index: 2, }, }, } @@ -362,16 +364,16 @@ func TestDelegate_ValidLog(t *testing.T) { vuni.ec.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(generateCallbackReturnValues(t, false), nil) // Ensure we queue up a valid eth transaction - // Linked to requestID + // Linked to requestID vuni.txm.On("CreateEthTransaction", - mock.MatchedBy(func(newTx bulletprooftxmanager.NewTx) bool { + mock.MatchedBy(func(newTx txmgr.NewTx) bool { meta := newTx.Meta return newTx.FromAddress == vuni.submitter && newTx.ToAddress == common.HexToAddress(jb.VRFSpec.CoordinatorAddress.String()) && newTx.GasLimit == uint64(500000) && (meta.JobID > 0 && meta.RequestID == tc.reqID && meta.RequestTxHash == txHash) }), - ).Once().Return(bulletprooftxmanager.EthTx{}, nil) + ).Once().Return(txmgr.EthTx{}, nil) listener.HandleLog(log.NewLogBroadcast(tc.log, vuni.cid, nil)) // Wait until the log is present @@ -406,6 +408,7 @@ func TestDelegate_ValidLog(t *testing.T) { ), BlockNumber: 10, TxHash: txHash, + Index: uint(i), }, vuni.cid, &solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled{RequestId: tc.reqID})) waitForChannel(t, consumed, 2*time.Second, "fulfillment log not marked consumed") // Should record that we've responded to this request @@ -470,7 +473,7 @@ func TestDelegate_InvalidLog(t *testing.T) { } // Ensure we have NOT queued up an eth transaction - var ethTxes []bulletprooftxmanager.EthTx + var ethTxes []txmgr.EthTx err = vuni.prm.GetQ().Select(ðTxes, `SELECT * FROM eth_txes;`) require.NoError(t, err) require.Len(t, ethTxes, 0) diff --git a/core/services/vrf/integration_test.go b/core/services/vrf/integration_test.go index 00f1501b406..533098e7017 100644 --- a/core/services/vrf/integration_test.go +++ b/core/services/vrf/integration_test.go @@ -1,6 +1,7 @@ package vrf_test import ( + "context" "encoding/hex" "fmt" "math/big" @@ -16,10 +17,11 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/core/services/pg" @@ -43,11 +45,14 @@ func TestIntegration_VRF_JPV2(t *testing.T) { t.Run(test.name, func(t *testing.T) { config, _ := heavyweight.FullTestDB(t, fmt.Sprintf("vrf_jpv2_%v", test.eip1559), true, true) config.Overrides.GlobalEvmEIP1559DynamicFees = null.BoolFrom(test.eip1559) - key := cltest.MustGenerateRandomKey(t) - cu := newVRFCoordinatorUniverse(t, key) + key1 := cltest.MustGenerateRandomKey(t) + key2 := cltest.MustGenerateRandomKey(t) + cu := newVRFCoordinatorUniverse(t, key1, key2) incomingConfs := 2 - app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, cu.backend, key) - require.NoError(t, app.Start()) + app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, cu.backend, key1) + require.NoError(t, app.Start(testutils.Context(t))) + + cltest.MustAddKeyToKeystore(t, key2, cu.backend.Blockchain().Config().ChainID, app.KeyStore.Eth()) jb, vrfKey := createVRFJobRegisterKey(t, cu, app, incomingConfs) require.NoError(t, app.JobSpawner().CreateJob(&jb)) @@ -55,8 +60,12 @@ func TestIntegration_VRF_JPV2(t *testing.T) { _, err := cu.consumerContract.TestRequestRandomness(cu.carol, vrfKey.PublicKey.MustHash(), big.NewInt(100)) require.NoError(t, err) + + _, err = cu.consumerContract.TestRequestRandomness(cu.carol, + vrfKey.PublicKey.MustHash(), big.NewInt(100)) + require.NoError(t, err) cu.backend.Commit() - t.Log("Sent test request") + t.Log("Sent 2 test requests") // Mine the required number of blocks // So our request gets confirmed. for i := 0; i < incomingConfs; i++ { @@ -71,30 +80,42 @@ func TestIntegration_VRF_JPV2(t *testing.T) { // the lb will backfill the logs. However we need to // keep blocks coming in for the lb to send the backfilled logs. cu.backend.Commit() - return len(runs) == 1 && runs[0].State == pipeline.RunStatusCompleted + return len(runs) == 2 && runs[0].State == pipeline.RunStatusCompleted && runs[1].State == pipeline.RunStatusCompleted }, cltest.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) assert.Equal(t, pipeline.RunErrors([]null.String{{}}), runs[0].FatalErrors) assert.Equal(t, 4, len(runs[0].PipelineTaskRuns)) + assert.Equal(t, 4, len(runs[1].PipelineTaskRuns)) assert.NotNil(t, 0, runs[0].Outputs.Val) + assert.NotNil(t, 0, runs[1].Outputs.Val) // Ensure the eth transaction gets confirmed on chain. gomega.NewWithT(t).Eventually(func() bool { q := pg.NewQ(app.GetSqlxDB(), app.GetLogger(), app.GetConfig()) - uc, err2 := bulletprooftxmanager.CountUnconfirmedTransactions(q, key.Address.Address(), cltest.FixtureChainID) + uc, err2 := txmgr.CountUnconfirmedTransactions(q, key1.Address.Address(), cltest.FixtureChainID) require.NoError(t, err2) return uc == 0 }, cltest.WaitTimeout(t), 100*time.Millisecond).Should(gomega.BeTrue()) // Assert the request was fulfilled on-chain. + var rf []*solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled gomega.NewWithT(t).Eventually(func() bool { rfIterator, err := cu.rootContract.FilterRandomnessRequestFulfilled(nil) require.NoError(t, err, "failed to subscribe to RandomnessRequest logs") - var rf []*solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequestFulfilled + rf = nil for rfIterator.Next() { rf = append(rf, rfIterator.Event) } - return len(rf) == 1 + return len(rf) == 2 }, cltest.WaitTimeout(t), 500*time.Millisecond).Should(gomega.BeTrue()) + + // Check that each sending address sent one transaction + n1, err := cu.backend.PendingNonceAt(context.Background(), key1.Address.Address()) + require.NoError(t, err) + require.EqualValues(t, 1, n1) + + n2, err := cu.backend.PendingNonceAt(context.Background(), key2.Address.Address()) + require.NoError(t, err) + require.EqualValues(t, 1, n2) }) } } @@ -107,7 +128,7 @@ func TestIntegration_VRF_WithBHS(t *testing.T) { incomingConfs := 2 config.Overrides.BlockBackfillDepth = null.IntFrom(500) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, cu.backend, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Create VRF job but do not start it yet jb, vrfKey := createVRFJobRegisterKey(t, cu, app, incomingConfs) @@ -170,7 +191,7 @@ func TestIntegration_VRF_WithBHS(t *testing.T) { // Ensure the eth transaction gets confirmed on chain. gomega.NewWithT(t).Eventually(func() bool { q := pg.NewQ(app.GetSqlxDB(), app.GetLogger(), app.GetConfig()) - uc, err2 := bulletprooftxmanager.CountUnconfirmedTransactions(q, key.Address.Address(), cltest.FixtureChainID) + uc, err2 := txmgr.CountUnconfirmedTransactions(q, key.Address.Address(), cltest.FixtureChainID) require.NoError(t, err2) return uc == 0 }, 5*time.Second, 100*time.Millisecond).Should(gomega.BeTrue()) diff --git a/core/services/vrf/integration_v2_test.go b/core/services/vrf/integration_v2_test.go index ff96448735f..406c53387aa 100644 --- a/core/services/vrf/integration_v2_test.go +++ b/core/services/vrf/integration_v2_test.go @@ -11,10 +11,6 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/blockhash_store" - "github.com/smartcontractkit/chainlink/core/internal/testutils" - "github.com/smartcontractkit/sqlx" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -29,11 +25,14 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/blockhash_store" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/mock_v3_aggregator_contract" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_consumer_v2" @@ -42,6 +41,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_malicious_consumer_v2" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_single_consumer_example" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrfv2_reverting_example" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore" @@ -104,9 +104,7 @@ var ( ) func newVRFCoordinatorV2Universe(t *testing.T, key ethkey.KeyV2, numConsumers int) coordinatorV2Universe { - if testing.Short() { - t.Skip("skipping due to VRFCoordinatorV2Universe") - } + testutils.SkipShort(t, "VRFCoordinatorV2Universe") oracleTransactor := cltest.MustNewSimulatedBackendKeyedTransactor(t, key.ToEcdsaPrivKey()) var ( sergey = newIdentity(t) @@ -295,9 +293,14 @@ func subscribeVRF( return sub, subID } -func createVRFJobs(t *testing.T, keys []ethkey.KeyV2, app *cltest.TestApplication, uni coordinatorV2Universe) (jobs []job.Job) { +func createVRFJobs(t *testing.T, fromKeys [][]ethkey.KeyV2, app *cltest.TestApplication, uni coordinatorV2Universe) (jobs []job.Job) { // Create separate jobs for each gas lane and register their keys - for i, key := range keys { + for i, keys := range fromKeys { + var keyStrs []string + for _, k := range keys { + keyStrs = append(keyStrs, k.Address.String()) + } + vrfkey, err := app.GetKeyStore().VRF().Create() require.NoError(t, err) @@ -309,7 +312,7 @@ func createVRFJobs(t *testing.T, keys []ethkey.KeyV2, app *cltest.TestApplicatio CoordinatorAddress: uni.rootContractAddress.String(), MinIncomingConfirmations: incomingConfs, PublicKey: vrfkey.PublicKey.String(), - FromAddress: key.Address.String(), + FromAddresses: keyStrs, V2: true, }).Toml() jb, err := vrf.ValidatedVRFSpec(s) @@ -329,7 +332,7 @@ func createVRFJobs(t *testing.T, keys []ethkey.KeyV2, app *cltest.TestApplicatio count++ } } - return count == len(keys) + return count == len(fromKeys) }, cltest.WaitTimeout(t), 100*time.Millisecond).Should(gomega.BeTrue()) // Unfortunately the lb needs heads to be able to backfill logs to new subscribers. // To avoid confirming @@ -466,7 +469,7 @@ func assertNumRandomWords( func mine(t *testing.T, requestID *big.Int, subID uint64, uni coordinatorV2Universe, db *sqlx.DB) bool { return gomega.NewWithT(t).Eventually(func() bool { uni.backend.Commit() - var txs []bulletprooftxmanager.EthTx + var txs []txmgr.EthTx err := db.Select(&txs, ` SELECT * FROM eth_txes WHERE eth_txes.state = 'confirmed' @@ -494,23 +497,29 @@ func TestVRFV2Integration_SingleConsumer_HappyPath(t *testing.T) { subID := subscribeAndAssertSubscriptionCreatedEvent(t, consumerContract, consumer, consumerContractAddress, big.NewInt(5e18), uni) // Create gas lane. - key, err := app.KeyStore.Eth().Create(big.NewInt(1337)) + key1, err := app.KeyStore.Eth().Create(big.NewInt(1337)) require.NoError(t, err) - sendEth(t, ownerKey, uni.backend, key.Address.Address(), 10) - configureSimChain(app, map[string]types.ChainCfg{ - key.Address.String(): { + key2, err := app.KeyStore.Eth().Create(big.NewInt(1337)) + require.NoError(t, err) + sendEth(t, ownerKey, uni.backend, key1.Address.Address(), 10) + sendEth(t, ownerKey, uni.backend, key2.Address.Address(), 10) + configureSimChain(t, app, map[string]types.ChainCfg{ + key1.Address.String(): { + EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10e9)), // 10 gwei + }, + key2.Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10e9)), // 10 gwei }, }, big.NewInt(10e9)) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) - // Create VRF job. - jbs := createVRFJobs(t, []ethkey.KeyV2{key}, app, uni) + // Create VRF job using key1 and key2 on the same gas lane. + jbs := createVRFJobs(t, [][]ethkey.KeyV2{{key1, key2}}, app, uni) keyHash := jbs[0].VRFSpec.PublicKey.MustHash() - // Make the randomness request. + // Make the first randomness request. numWords := uint32(20) - requestID, _ := requestRandomnessAndAssertRandomWordsRequestedEvent(t, consumerContract, consumer, keyHash, subID, numWords, uni) + requestID1, _ := requestRandomnessAndAssertRandomWordsRequestedEvent(t, consumerContract, consumer, keyHash, subID, numWords, uni) // Wait for fulfillment to be queued. gomega.NewGomegaWithT(t).Eventually(func() bool { @@ -522,14 +531,35 @@ func TestVRFV2Integration_SingleConsumer_HappyPath(t *testing.T) { }, cltest.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID, subID, uni, db) + mine(t, requestID1, subID, uni, db) // Assert correct state of RandomWordsFulfilled event. - assertRandomWordsFulfilled(t, requestID, true, uni) + assertRandomWordsFulfilled(t, requestID1, true, uni) + + // Make the second randomness request and assert fulfillment is successful + requestID2, _ := requestRandomnessAndAssertRandomWordsRequestedEvent(t, consumerContract, consumer, keyHash, subID, numWords, uni) + gomega.NewGomegaWithT(t).Eventually(func() bool { + uni.backend.Commit() + runs, err := app.PipelineORM().GetAllRuns() + require.NoError(t, err) + t.Log("runs", len(runs)) + return len(runs) == 2 + }, cltest.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) + mine(t, requestID2, subID, uni, db) + assertRandomWordsFulfilled(t, requestID2, true, uni) // Assert correct number of random words sent by coordinator. assertNumRandomWords(t, consumerContract, numWords) + // Assert that both send addresses were used to fulfill the requests + n, err := uni.backend.PendingNonceAt(context.Background(), key1.Address.Address()) + require.NoError(t, err) + require.EqualValues(t, 1, n) + + n, err = uni.backend.PendingNonceAt(context.Background(), key2.Address.Address()) + require.NoError(t, err) + require.EqualValues(t, 1, n) + t.Log("Done!") } @@ -551,7 +581,7 @@ func TestVRFV2Integration_SingleConsumer_NeedsBlockhashStore(t *testing.T) { vrfKey, err := app.KeyStore.Eth().Create(big.NewInt(1337)) require.NoError(t, err) sendEth(t, ownerKey, uni.backend, vrfKey.Address.Address(), 10) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Create BHS key bhsKey, err := app.KeyStore.Eth().Create(big.NewInt(1337)) @@ -559,7 +589,7 @@ func TestVRFV2Integration_SingleConsumer_NeedsBlockhashStore(t *testing.T) { sendEth(t, ownerKey, uni.backend, bhsKey.Address.Address(), 10) // Configure VRF and BHS keys - configureSimChain(app, map[string]types.ChainCfg{ + configureSimChain(t, app, map[string]types.ChainCfg{ vrfKey.Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10e9)), // 10 gwei }, @@ -569,7 +599,7 @@ func TestVRFV2Integration_SingleConsumer_NeedsBlockhashStore(t *testing.T) { }, big.NewInt(10e9)) // Create VRF job. - vrfJobs := createVRFJobs(t, []ethkey.KeyV2{vrfKey}, app, uni) + vrfJobs := createVRFJobs(t, [][]ethkey.KeyV2{{vrfKey}}, app, uni) keyHash := vrfJobs[0].VRFSpec.PublicKey.MustHash() _ = createAndStartBHSJob( @@ -650,15 +680,15 @@ func TestVRFV2Integration_SingleConsumer_NeedsTopUp(t *testing.T) { key, err := app.KeyStore.Eth().Create(big.NewInt(1337)) require.NoError(t, err) sendEth(t, ownerKey, uni.backend, key.Address.Address(), 10) - configureSimChain(app, map[string]types.ChainCfg{ + configureSimChain(t, app, map[string]types.ChainCfg{ key.Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(1000e9)), // 1000 gwei }, }, big.NewInt(1000e9)) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Create VRF job. - jbs := createVRFJobs(t, []ethkey.KeyV2{key}, app, uni) + jbs := createVRFJobs(t, [][]ethkey.KeyV2{{key}}, app, uni) keyHash := jbs[0].VRFSpec.PublicKey.MustHash() numWords := uint32(20) @@ -686,7 +716,7 @@ func TestVRFV2Integration_SingleConsumer_NeedsTopUp(t *testing.T) { return len(runs) == 1 }, cltest.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) - // Mine the fulfillment. Need to wait for BPTXM to mark the tx as confirmed + // Mine the fulfillment. Need to wait for Txm to mark the tx as confirmed // so that we can actually see the event on the simulated chain. mine(t, requestID, subID, uni, db) @@ -719,7 +749,7 @@ func TestVRFV2Integration_SingleConsumer_MultipleGasLanes(t *testing.T) { expensiveKey, err := app.KeyStore.Eth().Create(big.NewInt(1337)) require.NoError(t, err) sendEth(t, ownerKey, uni.backend, expensiveKey.Address.Address(), 10) - configureSimChain(app, map[string]types.ChainCfg{ + configureSimChain(t, app, map[string]types.ChainCfg{ cheapKey.Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10e9)), // 10 gwei }, @@ -727,10 +757,10 @@ func TestVRFV2Integration_SingleConsumer_MultipleGasLanes(t *testing.T) { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(1000e9)), // 1000 gwei }, }, big.NewInt(10e9)) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Create VRF jobs. - jbs := createVRFJobs(t, []ethkey.KeyV2{cheapKey, expensiveKey}, app, uni) + jbs := createVRFJobs(t, [][]ethkey.KeyV2{{cheapKey}, {expensiveKey}}, app, uni) cheapHash := jbs[0].VRFSpec.PublicKey.MustHash() expensiveHash := jbs[1].VRFSpec.PublicKey.MustHash() @@ -807,15 +837,15 @@ func TestVRFV2Integration_SingleConsumer_AlwaysRevertingCallback_StillFulfilled( key, err := app.KeyStore.Eth().Create(big.NewInt(1337)) require.NoError(t, err) sendEth(t, ownerKey, uni.backend, key.Address.Address(), 10) - configureSimChain(app, map[string]types.ChainCfg{ + configureSimChain(t, app, map[string]types.ChainCfg{ key.Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10e9)), // 10 gwei }, }, big.NewInt(10e9)) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Create VRF job. - jbs := createVRFJobs(t, []ethkey.KeyV2{key}, app, uni) + jbs := createVRFJobs(t, [][]ethkey.KeyV2{{key}}, app, uni) keyHash := jbs[0].VRFSpec.PublicKey.MustHash() // Make the randomness request. @@ -839,10 +869,11 @@ func TestVRFV2Integration_SingleConsumer_AlwaysRevertingCallback_StillFulfilled( t.Log("Done!") } -func configureSimChain(app *cltest.TestApplication, ks map[string]types.ChainCfg, defaultGasPrice *big.Int) { +func configureSimChain(t *testing.T, app *cltest.TestApplication, ks map[string]types.ChainCfg, defaultGasPrice *big.Int) { zero := models.MustMakeDuration(0 * time.Millisecond) reaperThreshold := models.MustMakeDuration(100 * time.Millisecond) app.Chains.EVM.Configure( + testutils.Context(t), big.NewInt(1337), true, types.ChainCfg{ @@ -990,13 +1021,13 @@ func TestIntegrationVRFV2(t *testing.T) { // max gas limit of 2M and a key specific max 10 gwei price. // Keep the prices low so we can operate with small link balance subscriptions. gasPrice := decimal.NewFromBigInt(big.NewInt(1000000000), 0) - configureSimChain(app, map[string]types.ChainCfg{ + configureSimChain(t, app, map[string]types.ChainCfg{ keys[0].Address.String(): { EvmMaxGasPriceWei: utils.NewBig(big.NewInt(10000000000)), }, }, gasPrice.BigInt()) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) vrfkey, err := app.GetKeyStore().VRF().Create() require.NoError(t, err) @@ -1008,7 +1039,7 @@ func TestIntegrationVRFV2(t *testing.T) { CoordinatorAddress: uni.rootContractAddress.String(), MinIncomingConfirmations: incomingConfs, PublicKey: vrfkey.PublicKey.String(), - FromAddress: keys[0].Address.String(), + FromAddresses: []string{keys[0].Address.String()}, V2: true, }).Toml() jb, err := vrf.ValidatedVRFSpec(s) @@ -1174,7 +1205,7 @@ func TestMaliciousConsumer(t *testing.T) { config.Overrides.GlobalEvmGasFeeCapDefault = assets.GWei(1) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, uni.backend, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) err := app.GetKeyStore().Unlock(cltest.Password) require.NoError(t, err) @@ -1219,10 +1250,10 @@ func TestMaliciousConsumer(t *testing.T) { // We expect the request to be serviced // by the node. - var attempts []bulletprooftxmanager.EthTxAttempt + var attempts []txmgr.EthTxAttempt gomega.NewWithT(t).Eventually(func() bool { //runs, err = app.PipelineORM().GetAllRuns() - attempts, _, err = app.BPTXMORM().EthTxAttempts(0, 1000) + attempts, _, err = app.TxmORM().EthTxAttempts(0, 1000) require.NoError(t, err) // It possible that we send the test request // before the job spawner has started the vrf services, which is fine @@ -1230,7 +1261,7 @@ func TestMaliciousConsumer(t *testing.T) { // keep blocks coming in for the lb to send the backfilled logs. t.Log("attempts", attempts) uni.backend.Commit() - return len(attempts) == 1 && attempts[0].EthTx.State == bulletprooftxmanager.EthTxConfirmed + return len(attempts) == 1 && attempts[0].EthTx.State == txmgr.EthTxConfirmed }, cltest.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) // The fulfillment tx should succeed @@ -1269,7 +1300,7 @@ func TestRequestCost(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, cfg, uni.backend, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) vrfkey, err := app.GetKeyStore().VRF().Create() require.NoError(t, err) @@ -1312,7 +1343,7 @@ func TestMaxConsumersCost(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, cfg, uni.backend, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) _, err := carolContract.TestCreateSubscriptionAndFund(carol, big.NewInt(1000000000000000000)) // 0.1 LINK require.NoError(t, err) @@ -1347,7 +1378,7 @@ func TestFulfillmentCost(t *testing.T) { cfg := cltest.NewTestGeneralConfig(t) app := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, cfg, uni.backend, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) vrfkey, err := app.GetKeyStore().VRF().Create() require.NoError(t, err) @@ -1415,27 +1446,27 @@ func TestStartingCountsV1(t *testing.T) { require.NoError(t, err) b := time.Now() n1, n2, n3, n4 := int64(0), int64(1), int64(2), int64(3) - m1 := bulletprooftxmanager.EthTxMeta{ + m1 := txmgr.EthTxMeta{ RequestID: utils.PadByteToHash(0x10), } md1, err := json.Marshal(&m1) require.NoError(t, err) md1_ := datatypes.JSON(md1) - m2 := bulletprooftxmanager.EthTxMeta{ + m2 := txmgr.EthTxMeta{ RequestID: utils.PadByteToHash(0x11), } md2, err := json.Marshal(&m2) md2_ := datatypes.JSON(md2) require.NoError(t, err) chainID := utils.NewBig(big.NewInt(1337)) - confirmedTxes := []bulletprooftxmanager.EthTx{ + confirmedTxes := []txmgr.EthTx{ { Nonce: &n1, FromAddress: k.Address.Address(), Error: null.String{}, BroadcastAt: &b, CreatedAt: b, - State: bulletprooftxmanager.EthTxConfirmed, + State: txmgr.EthTxConfirmed, Meta: &datatypes.JSON{}, EncodedPayload: []byte{}, EVMChainID: *chainID, @@ -1446,7 +1477,7 @@ func TestStartingCountsV1(t *testing.T) { Error: null.String{}, BroadcastAt: &b, CreatedAt: b, - State: bulletprooftxmanager.EthTxConfirmed, + State: txmgr.EthTxConfirmed, Meta: &md1_, EncodedPayload: []byte{}, EVMChainID: *chainID, @@ -1457,7 +1488,7 @@ func TestStartingCountsV1(t *testing.T) { Error: null.String{}, BroadcastAt: &b, CreatedAt: b, - State: bulletprooftxmanager.EthTxConfirmed, + State: txmgr.EthTxConfirmed, Meta: &md2_, EncodedPayload: []byte{}, EVMChainID: *chainID, @@ -1468,27 +1499,27 @@ func TestStartingCountsV1(t *testing.T) { Error: null.String{}, BroadcastAt: &b, CreatedAt: b, - State: bulletprooftxmanager.EthTxConfirmed, + State: txmgr.EthTxConfirmed, Meta: &md2_, EncodedPayload: []byte{}, EVMChainID: *chainID, }, } // add unconfirmed txes - unconfirmedTxes := []bulletprooftxmanager.EthTx{} + unconfirmedTxes := []txmgr.EthTx{} for i := int64(4); i < 6; i++ { - md, err := json.Marshal(&bulletprooftxmanager.EthTxMeta{ + md, err := json.Marshal(&txmgr.EthTxMeta{ RequestID: utils.PadByteToHash(0x12), }) require.NoError(t, err) md1 := datatypes.JSON(md) newNonce := i + 1 - unconfirmedTxes = append(unconfirmedTxes, bulletprooftxmanager.EthTx{ + unconfirmedTxes = append(unconfirmedTxes, txmgr.EthTx{ Nonce: &newNonce, FromAddress: k.Address.Address(), Error: null.String{}, CreatedAt: b, - State: bulletprooftxmanager.EthTxUnconfirmed, + State: txmgr.EthTxUnconfirmed, BroadcastAt: &b, Meta: &md1, EncodedPayload: []byte{}, @@ -1505,27 +1536,27 @@ func TestStartingCountsV1(t *testing.T) { // add eth_tx_attempts for confirmed broadcastBlock := int64(1) - txAttempts := []bulletprooftxmanager.EthTxAttempt{} + txAttempts := []txmgr.EthTxAttempt{} for i := range confirmedTxes { - txAttempts = append(txAttempts, bulletprooftxmanager.EthTxAttempt{ + txAttempts = append(txAttempts, txmgr.EthTxAttempt{ EthTxID: int64(i + 1), GasPrice: utils.NewBig(big.NewInt(100)), SignedRawTx: []byte(`blah`), Hash: utils.NewHash(), BroadcastBeforeBlockNum: &broadcastBlock, - State: bulletprooftxmanager.EthTxAttemptBroadcast, + State: txmgr.EthTxAttemptBroadcast, CreatedAt: time.Now(), ChainSpecificGasLimit: uint64(100), }) } // add eth_tx_attempts for unconfirmed for i := range unconfirmedTxes { - txAttempts = append(txAttempts, bulletprooftxmanager.EthTxAttempt{ + txAttempts = append(txAttempts, txmgr.EthTxAttempt{ EthTxID: int64(i + 1 + len(confirmedTxes)), GasPrice: utils.NewBig(big.NewInt(100)), SignedRawTx: []byte(`blah`), Hash: utils.NewHash(), - State: bulletprooftxmanager.EthTxAttemptInProgress, + State: txmgr.EthTxAttemptInProgress, CreatedAt: time.Now(), ChainSpecificGasLimit: uint64(100), }) @@ -1541,9 +1572,9 @@ func TestStartingCountsV1(t *testing.T) { } // add eth_receipts - receipts := []bulletprooftxmanager.EthReceipt{} + receipts := []txmgr.EthReceipt{} for i := 0; i < 4; i++ { - receipts = append(receipts, bulletprooftxmanager.EthReceipt{ + receipts = append(receipts, txmgr.EthReceipt{ BlockHash: utils.NewHash(), TxHash: txAttempts[i].Hash, BlockNumber: broadcastBlock, diff --git a/core/services/vrf/listener_v1.go b/core/services/vrf/listener_v1.go index b87be167d52..014314784c1 100644 --- a/core/services/vrf/listener_v1.go +++ b/core/services/vrf/listener_v1.go @@ -5,14 +5,15 @@ import ( "encoding/hex" "fmt" "sync" + "time" "github.com/ethereum/go-ethereum/common" heaps "github.com/theodesp/go-heaps" "github.com/theodesp/go-heaps/pairing" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/core/logger" @@ -24,14 +25,15 @@ import ( ) var ( - _ log.Listener = &listenerV1{} - _ job.Service = &listenerV1{} + _ log.Listener = &listenerV1{} + _ job.ServiceCtx = &listenerV1{} ) type request struct { confirmedAtBlock uint64 req *solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest lb log.Broadcast + utcTimestamp time.Time } type listenerV1 struct { @@ -45,7 +47,7 @@ type listenerV1 struct { job job.Job q pg.Q headBroadcaster httypes.HeadBroadcasterRegistry - txm bulletprooftxmanager.TxManager + txm txmgr.TxManager gethks GethKeyStore reqLogs *utils.Mailbox chStop chan struct{} @@ -68,6 +70,9 @@ type listenerV1 struct { // respCount map - we repeatedly want remove the minimum log. // You could use a sorted list if the completed logs arrive in order, but they may not. blockNumberToReqID *pairing.PairHeap + + // deduper prevents processing duplicate requests from the log broadcaster. + deduper *logDeduper } // Note that we have 2 seconds to do this processing @@ -95,7 +100,7 @@ func (lsn *listenerV1) getLatestHead() uint64 { } // Start complies with job.Service -func (lsn *listenerV1) Start() error { +func (lsn *listenerV1) Start(context.Context) error { return lsn.StartOnce("VRFListener", func() error { spec := job.LoadEnvConfigVarsVRF(lsn.cfg, *lsn.job.VRFSpec) @@ -135,6 +140,7 @@ func (lsn *listenerV1) Start() error { func (lsn *listenerV1) extractConfirmedLogs() []request { lsn.reqsMu.Lock() defer lsn.reqsMu.Unlock() + updateQueueSize(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v1, len(lsn.reqs)) var toProcess, toKeep []request for i := 0; i < len(lsn.reqs); i++ { if lsn.reqs[i].confirmedAtBlock <= lsn.getLatestHead() { @@ -193,9 +199,15 @@ func (lsn *listenerV1) runHeadListener(unsubscribe func()) { case <-lsn.newHead: recovery.WrapRecover(lsn.l, func() { toProcess := lsn.extractConfirmedLogs() + var toRetry []request for _, r := range toProcess { - lsn.ProcessRequest(r.req, r.lb) + if success := lsn.ProcessRequest(r); !success { + toRetry = append(toRetry, r) + } } + lsn.reqsMu.Lock() + defer lsn.reqsMu.Unlock() + lsn.reqs = append(lsn.reqs, toRetry...) lsn.pruneConfirmedRequestCounts() }) } @@ -266,6 +278,7 @@ func (lsn *listenerV1) handleLog(lb log.Broadcast, minConfs uint32) { confirmedAtBlock: confirmedAt, req: req, lb: lb, + utcTimestamp: time.Now().UTC(), }) lsn.reqAdded() lsn.reqsMu.Unlock() @@ -311,15 +324,17 @@ func (lsn *listenerV1) getConfirmedAt(req *solidity_vrf_coordinator_interface.VR "blockHash", req.Raw.BlockHash, "reqID", hex.EncodeToString(req.RequestID[:]), "newConfs", newConfs) + incDupeReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v1) } return req.Raw.BlockNumber + newConfs } -func (lsn *listenerV1) ProcessRequest(req *solidity_vrf_coordinator_interface.VRFCoordinatorRandomnessRequest, lb log.Broadcast) { +// ProcessRequest attempts to process the VRF request. Returns true if successful, false otherwise. +func (lsn *listenerV1) ProcessRequest(req request) bool { // This check to see if the log was consumed needs to be in the same // goroutine as the mark consumed to avoid processing duplicates. - if !lsn.shouldProcessLog(lb) { - return + if !lsn.shouldProcessLog(req.lb) { + return true } // Check if the vrf req has already been fulfilled @@ -332,26 +347,34 @@ func (lsn *listenerV1) ProcessRequest(req *solidity_vrf_coordinator_interface.VR // 4. The eth node sees the request reorg and tells us about it. We do our fulfillment // check and the node says its already fulfilled (hasn't seen the fulfillment reorged yet), // so we don't process the request. - callback, err := lsn.coordinator.Callbacks(nil, req.RequestID) + callback, err := lsn.coordinator.Callbacks(nil, req.req.RequestID) if err != nil { - lsn.l.Errorw("Unable to check if already fulfilled, processing anyways", "err", err, "txHash", req.Raw.TxHash) + lsn.l.Errorw("Unable to check if already fulfilled, processing anyways", "err", err, "txHash", req.req.Raw.TxHash) } else if utils.IsEmpty(callback.SeedAndBlockNum[:]) { // If seedAndBlockNumber is zero then the response has been fulfilled // and we should skip it - lsn.l.Infow("Request already fulfilled", "txHash", req.Raw.TxHash, "reqID", req.RequestID) - lsn.markLogAsConsumed(lb) - return + lsn.l.Infow("Request already fulfilled", "txHash", req.req.Raw.TxHash, "reqID", req.req.RequestID) + lsn.markLogAsConsumed(req.lb) + return true + } + + // Check if we can ignore the request due to its age. + if time.Now().UTC().Sub(req.utcTimestamp) >= lsn.job.VRFSpec.RequestTimeout { + lsn.l.Infow("Request too old, dropping it", + "txHash", req.req.Raw.TxHash, "reqID", req.req.RequestID) + lsn.markLogAsConsumed(req.lb) + return true } lsn.l.Infow("Received log request", - "log", lb.String(), - "reqID", hex.EncodeToString(req.RequestID[:]), - "keyHash", hex.EncodeToString(req.KeyHash[:]), - "txHash", req.Raw.TxHash, - "blockNumber", req.Raw.BlockNumber, - "blockHash", req.Raw.BlockHash, - "seed", req.Seed, - "fee", req.Fee) + "log", req.lb.String(), + "reqID", hex.EncodeToString(req.req.RequestID[:]), + "keyHash", hex.EncodeToString(req.req.KeyHash[:]), + "txHash", req.req.Raw.TxHash, + "blockNumber", req.req.Raw.BlockNumber, + "blockHash", req.req.Raw.BlockHash, + "seed", req.req.Seed, + "fee", req.req.Fee) vars := pipeline.NewVarsFrom(map[string]interface{}{ "jobSpec": map[string]interface{}{ @@ -359,13 +382,14 @@ func (lsn *listenerV1) ProcessRequest(req *solidity_vrf_coordinator_interface.VR "externalJobID": lsn.job.ExternalJobID, "name": lsn.job.Name.ValueOrZero(), "publicKey": lsn.job.VRFSpec.PublicKey[:], + "from": lsn.fromAddresses(), }, "jobRun": map[string]interface{}{ - "logBlockHash": req.Raw.BlockHash[:], - "logBlockNumber": req.Raw.BlockNumber, - "logTxHash": req.Raw.TxHash, - "logTopics": req.Raw.Topics, - "logData": req.Raw.Data, + "logBlockHash": req.req.Raw.BlockHash[:], + "logBlockNumber": req.req.Raw.BlockNumber, + "logTxHash": req.req.Raw.TxHash, + "logTopics": req.req.Raw.Topics, + "logData": req.req.Raw.Data, }, }) @@ -373,30 +397,34 @@ func (lsn *listenerV1) ProcessRequest(req *solidity_vrf_coordinator_interface.VR // The VRF pipeline has no async tasks, so we don't need to check for `incomplete` if _, err = lsn.pipelineRunner.Run(context.Background(), &run, lsn.l, true, func(tx pg.Queryer) error { // Always mark consumed regardless of whether the proof failed or not. - if err = lsn.logBroadcaster.MarkConsumed(lb, pg.WithQueryer(tx)); err != nil { + if err = lsn.logBroadcaster.MarkConsumed(req.lb, pg.WithQueryer(tx)); err != nil { lsn.l.Errorw("Failed mark consumed", "err", err) } return nil }); err != nil { lsn.l.Errorw("Failed executing run", "err", err, - "reqID", hex.EncodeToString(req.RequestID[:]), - "reqTxHash", req.Raw.TxHash) + "reqID", hex.EncodeToString(req.req.RequestID[:]), + "reqTxHash", req.req.Raw.TxHash) + return false } else { if run.HasErrors() || run.HasFatalErrors() { lsn.l.Error("VRFV1 pipeline run failed with errors", - "reqID", hex.EncodeToString(req.RequestID[:]), - "keyHash", hex.EncodeToString(req.KeyHash[:]), - "reqTxHash", req.Raw.TxHash, + "reqID", hex.EncodeToString(req.req.RequestID[:]), + "keyHash", hex.EncodeToString(req.req.KeyHash[:]), + "reqTxHash", req.req.Raw.TxHash, "runErrors", run.AllErrors.ToError(), "runFatalErrors", run.FatalErrors.ToError(), ) + return false } else { lsn.l.Debugw("Executed VRFV1 fulfillment run", - "reqID", hex.EncodeToString(req.RequestID[:]), - "keyHash", hex.EncodeToString(req.KeyHash[:]), - "reqTxHash", req.Raw.TxHash, + "reqID", hex.EncodeToString(req.req.RequestID[:]), + "keyHash", hex.EncodeToString(req.req.KeyHash[:]), + "reqTxHash", req.req.Raw.TxHash, ) + incProcessedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v1) + return true } } } @@ -412,10 +440,24 @@ func (lsn *listenerV1) Close() error { } func (lsn *listenerV1) HandleLog(lb log.Broadcast) { + if !lsn.deduper.shouldDeliver(lb.RawLog()) { + lsn.l.Tracew("skipping duplicate log broadcast", "log", lb.RawLog()) + return + } + wasOverCapacity := lsn.reqLogs.Deliver(lb) if wasOverCapacity { lsn.l.Error("l mailbox is over capacity - dropped the oldest l") + incDroppedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v1, reasonMailboxSize) + } +} + +func (lsn *listenerV1) fromAddresses() []common.Address { + var addresses []common.Address + for _, a := range lsn.job.VRFSpec.FromAddresses { + addresses = append(addresses, a.Address()) } + return addresses } // Job complies with log.Listener diff --git a/core/services/vrf/listener_v2.go b/core/services/vrf/listener_v2.go index 08ac88bbfec..e90e441d85f 100644 --- a/core/services/vrf/listener_v2.go +++ b/core/services/vrf/listener_v2.go @@ -15,10 +15,10 @@ import ( heaps "github.com/theodesp/go-heaps" "github.com/theodesp/go-heaps/pairing" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/aggregator_v3_interface" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_coordinator_v2" @@ -32,8 +32,8 @@ import ( ) var ( - _ log.Listener = &listenerV2{} - _ job.Service = &listenerV2{} + _ log.Listener = &listenerV2{} + _ job.ServiceCtx = &listenerV2{} ) const ( @@ -58,7 +58,7 @@ type listenerV2 struct { l logger.Logger ethClient evmclient.Client logBroadcaster log.Broadcaster - txm bulletprooftxmanager.TxManager + txm txmgr.TxManager coordinator *vrf_coordinator_v2.VRFCoordinatorV2 pipelineRunner pipeline.Runner job job.Job @@ -92,9 +92,13 @@ type listenerV2 struct { // aggregator client to get link/eth feed prices from chain. aggregator *aggregator_v3_interface.AggregatorV3Interface + + // deduper prevents processing duplicate requests from the log broadcaster. + deduper *logDeduper } -func (lsn *listenerV2) Start() error { +// Start starts listenerV2. +func (lsn *listenerV2) Start(context.Context) error { return lsn.StartOnce("VRFListenerV2", func() error { spec := job.LoadEnvConfigVarsVRF(lsn.cfg, *lsn.job.VRFSpec) @@ -108,7 +112,10 @@ func (lsn *listenerV2) Start() error { }, }, }, - // Do not specify min confirmations, as it varies from request to request. + // Specify a min incoming confirmations of 1 so that we can receive a request log + // right away. We set the real number of confirmations on a per-request basis in + // the getConfirmedAt method. + MinIncomingConfirmations: 1, }) latestHead, unsubscribeHeadBroadcaster := lsn.headBroadcaster.Subscribe(lsn) @@ -156,6 +163,7 @@ func (lsn *listenerV2) getLatestHead() uint64 { func (lsn *listenerV2) getAndRemoveConfirmedLogsBySub(latestHead uint64) map[uint64][]pendingRequest { lsn.reqsMu.Lock() defer lsn.reqsMu.Unlock() + updateQueueSize(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v2, uniqueReqs(lsn.reqs)) var toProcess = make(map[uint64][]pendingRequest) var toKeep []pendingRequest for i := 0; i < len(lsn.reqs); i++ { @@ -189,7 +197,7 @@ func (lsn *listenerV2) pruneConfirmedRequestCounts() { // Determine a set of logs that are confirmed // and the subscription has sufficient balance to fulfill, // given a eth call with the max gas price. -// Note we have to consider the pending reqs already in the bptxm as already "spent" link, +// Note we have to consider the pending reqs already in the txm as already "spent" link, // using a max link consumed in their metadata. // A user will need a minBalance capable of fulfilling a single req at the max gas price or nothing will happen. // This is acceptable as users can choose different keyhashes which have different max gas prices. @@ -203,31 +211,48 @@ func (lsn *listenerV2) pruneConfirmedRequestCounts() { // we simply retry TODO: follow up where if we see a fulfillment revert, return log to the queue. func (lsn *listenerV2) processPendingVRFRequests() { confirmed := lsn.getAndRemoveConfirmedLogsBySub(lsn.getLatestHead()) - keys, err := lsn.gethks.SendingKeys() - if err != nil { - lsn.l.Errorw("Unable to read sending keys", "err", err) - return - } - fromAddress := keys[0].Address - if lsn.job.VRFSpec.FromAddress != nil { - fromAddress = *lsn.job.VRFSpec.FromAddress - } - maxGasPriceWei := lsn.cfg.KeySpecificMaxGasPriceWei(fromAddress.Address()) + processed := make(map[string]struct{}) + start := time.Now() + + // Add any unprocessed requests back to lsn.reqs after request processing is complete. + defer func() { + var toKeep []pendingRequest + for _, subReqs := range confirmed { + for _, req := range subReqs { + if _, ok := processed[req.req.RequestId.String()]; !ok { + toKeep = append(toKeep, req) + } + } + } + // There could be logs accumulated to this slice while request processor is running, + // so we merged the new ones with the ones that need to be requeued. + lsn.reqsMu.Lock() + lsn.reqs = append(lsn.reqs, toKeep...) + lsn.reqsMu.Unlock() + lsn.l.Infow("Finished processing pending requests", + "total processed", len(processed), + "total unprocessed", len(toKeep), + "time", time.Since(start).String()) + }() + // TODO: also probably want to order these by request time so we service oldest first // Get subscription balance. Note that outside of this request handler, this can only decrease while there // are no pending requests if len(confirmed) == 0 { - lsn.l.Infow("No pending requests", "maxGasPrice", maxGasPriceWei, "fromAddress", fromAddress.Address()) + lsn.l.Infow("No pending requests") return } for subID, reqs := range confirmed { sub, err := lsn.coordinator.GetSubscription(nil, subID) if err != nil { lsn.l.Errorw("Unable to read subscription balance", "err", err) - return + continue } startBalance := sub.Balance - lsn.processRequestsPerSub(subID, fromAddress.Address(), startBalance, maxGasPriceWei, reqs) + p := lsn.processRequestsPerSub(subID, startBalance, reqs) + for reqID := range p { + processed[reqID] = struct{}{} + } } lsn.pruneConfirmedRequestCounts() } @@ -235,7 +260,7 @@ func (lsn *listenerV2) processPendingVRFRequests() { // MaybeSubtractReservedLink figures out how much LINK is reserved for other VRF requests that // have not been fully confirmed yet on-chain, and subtracts that from the given startBalance, // and returns that value if there are no errors. -func MaybeSubtractReservedLink(l logger.Logger, q pg.Q, fromAddress common.Address, startBalance *big.Int, chainID, subID uint64) (*big.Int, error) { +func MaybeSubtractReservedLink(l logger.Logger, q pg.Q, startBalance *big.Int, chainID, subID uint64) (*big.Int, error) { var reservedLink string err := q.Get(&reservedLink, `SELECT SUM(CAST(meta->>'MaxLink' AS NUMERIC(78, 0))) FROM eth_txes @@ -281,20 +306,20 @@ func (a fulfilledReqV2) Compare(b heaps.Item) int { func (lsn *listenerV2) processRequestsPerSub( subID uint64, - fromAddress common.Address, startBalance *big.Int, - maxGasPriceWei *big.Int, reqs []pendingRequest, -) { +) map[string]struct{} { + start := time.Now() + var processed = make(map[string]struct{}) startBalanceNoReserveLink, err := MaybeSubtractReservedLink( - lsn.l, lsn.q, fromAddress, startBalance, lsn.ethClient.ChainID().Uint64(), subID) + lsn.l, lsn.q, startBalance, lsn.ethClient.ChainID().Uint64(), subID) if err != nil { lsn.l.Errorw("Couldn't get reserved LINK for subscription", "sub", reqs[0].req.SubId) - return + return processed } + lggr := lsn.l.With( "subID", reqs[0].req.SubId, - "maxGasPrice", maxGasPriceWei.String(), "reqs", len(reqs), "startBalance", startBalance.String(), "startBalanceNoReservedLink", startBalanceNoReserveLink.String(), @@ -302,19 +327,26 @@ func (lsn *listenerV2) processRequestsPerSub( lggr.Infow("Processing requests for subscription") // Attempt to process every request, break if we run out of balance - var processed = make(map[string]struct{}) for _, req := range reqs { + fromAddress, err := lsn.gethks.GetRoundRobinAddress(lsn.fromAddresses()...) + if err != nil { + lggr.Errorw("Couldn't get next from address", "err", err) + continue + } + maxGasPriceWei := lsn.cfg.KeySpecificMaxGasPriceWei(fromAddress) + vrfRequest := req.req rlog := lggr.With( "reqID", vrfRequest.RequestId.String(), "txHash", vrfRequest.Raw.TxHash, - ) + "maxGasPrice", maxGasPriceWei.String(), + "fromAddress", fromAddress) // This check to see if the log was consumed needs to be in the same // goroutine as the mark consumed to avoid processing duplicates. consumed, err := lsn.logBroadcaster.WasAlreadyConsumed(req.lb) if err != nil { - // Do not process, let lb resend it as a retry mechanism. + // Do not process for now, retry on next iteration. rlog.Errorw("Could not determine if log was already consumed", "error", err) continue } else if consumed { @@ -327,6 +359,7 @@ func (lsn *listenerV2) processRequestsPerSub( rlog.Infow("Request too old, dropping it") lsn.markLogAsConsumed(req.lb) processed[vrfRequest.RequestId.String()] = struct{}{} + incDroppedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v2, reasonAge) continue } @@ -345,7 +378,7 @@ func (lsn *listenerV2) processRequestsPerSub( } // Run the pipeline to determine the max link that could be billed at maxGasPrice. // The ethcall will error if there is currently insufficient balance onchain. - maxLink, run, payload, gaslimit, err := lsn.getMaxLinkForFulfillment(maxGasPriceWei, req) + maxLink, run, payload, gaslimit, err := lsn.getMaxLinkForFulfillment(maxGasPriceWei, req, rlog) if err != nil { rlog.Warnw("Unable to get max link for fulfillment, skipping request", "err", err) continue @@ -357,7 +390,7 @@ func (lsn *listenerV2) processRequestsPerSub( break } rlog.Infow("Enqueuing fulfillment") - // We have enough balance to service it, lets enqueue for bptxm + // We have enough balance to service it, lets enqueue for txm err = lsn.q.Transaction(func(tx pg.Queryer) error { if err = lsn.pipelineRunner.InsertFinishedRun(&run, true, pg.WithQueryer(tx)); err != nil { return err @@ -365,20 +398,20 @@ func (lsn *listenerV2) processRequestsPerSub( if err = lsn.logBroadcaster.MarkConsumed(req.lb, pg.WithQueryer(tx)); err != nil { return err } - _, err = lsn.txm.CreateEthTransaction(bulletprooftxmanager.NewTx{ + _, err = lsn.txm.CreateEthTransaction(txmgr.NewTx{ FromAddress: fromAddress, ToAddress: lsn.coordinator.Address(), EncodedPayload: hexutil.MustDecode(payload), GasLimit: gaslimit, - Meta: &bulletprooftxmanager.EthTxMeta{ + Meta: &txmgr.EthTxMeta{ RequestID: common.BytesToHash(vrfRequest.RequestId.Bytes()), MaxLink: maxLink.String(), SubID: vrfRequest.SubId, }, MinConfirmations: null.Uint32From(uint32(lsn.cfg.MinRequiredOutgoingConfirmations())), - Strategy: bulletprooftxmanager.NewSendEveryStrategy(), - Checker: bulletprooftxmanager.TransmitCheckerSpec{ - CheckerType: bulletprooftxmanager.TransmitCheckerTypeVRFV2, + Strategy: txmgr.NewSendEveryStrategy(), + Checker: txmgr.TransmitCheckerSpec{ + CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: lsn.coordinator.Address(), }, }, pg.WithQueryer(tx)) @@ -388,29 +421,18 @@ func (lsn *listenerV2) processRequestsPerSub( rlog.Errorw("Error enqueuing fulfillment, requeuing request", "err", err) continue } - // If we successfully enqueued for the bptxm, subtract that balance + // If we successfully enqueued for the txm, subtract that balance // And loop to attempt to enqueue another fulfillment startBalanceNoReserveLink = startBalanceNoReserveLink.Sub(startBalanceNoReserveLink, maxLink) processed[vrfRequest.RequestId.String()] = struct{}{} + incProcessedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v2) } - // Remove all the confirmed logs - var toKeep []pendingRequest - for _, req := range reqs { - if _, ok := processed[req.req.RequestId.String()]; !ok { - toKeep = append(toKeep, req) - } - } - lsn.reqsMu.Lock() - // There could be logs accumulated to this slice while request processor is running, - // so we merged the new ones with the ones that need to be requeued. - lsn.reqs = append(lsn.reqs, toKeep...) - lsn.reqsMu.Unlock() lggr.Infow("Finished processing for sub", "total reqs", len(reqs), "total processed", len(processed), - "total remaining", len(toKeep), - "total unique", len(toRequestSet(reqs)), - ) + "total unique", uniqueReqs(reqs), + "time", time.Since(start).String()) + return processed } func (lsn *listenerV2) estimateFeeJuels( @@ -440,12 +462,16 @@ func (lsn *listenerV2) estimateFeeJuels( // Here we use the pipeline to parse the log, generate a vrf response // then simulate the transaction at the max gas price to determine its maximum link cost. -func (lsn *listenerV2) getMaxLinkForFulfillment(maxGasPriceWei *big.Int, req pendingRequest) (*big.Int, pipeline.Run, string, uint64, error) { +func (lsn *listenerV2) getMaxLinkForFulfillment( + maxGasPriceWei *big.Int, + req pendingRequest, + lg logger.Logger, +) (*big.Int, pipeline.Run, string, uint64, error) { // estimate how much juels are needed so that we can log it if the simulation fails. juelsNeeded, err := lsn.estimateFeeJuels(req.req, maxGasPriceWei) if err != nil { // not critical, just log and continue - lsn.l.Warnw("unable to estimate juels needed for request, continuing anyway", + lg.Warnw("unable to estimate juels needed for request, continuing anyway", "reqID", req.req.RequestId, "err", err, ) @@ -472,25 +498,25 @@ func (lsn *listenerV2) getMaxLinkForFulfillment(maxGasPriceWei *big.Int, req pen "logData": req.req.Raw.Data, }, }) - run, trrs, err := lsn.pipelineRunner.ExecuteRun(context.Background(), *lsn.job.PipelineSpec, vars, lsn.l) + run, trrs, err := lsn.pipelineRunner.ExecuteRun(context.Background(), *lsn.job.PipelineSpec, vars, lg) if err != nil { - lsn.l.Errorw("Failed executing run", "err", err) + lg.Errorw("Failed executing run", "err", err) return maxLink, run, payload, gaslimit, err } // The call task will fail if there are insufficient funds if run.AllErrors.HasError() { - lsn.l.Warnw("Simulation errored, possibly insufficient funds. Request will remain unprocessed until funds are available", + lg.Warnw("Simulation errored, possibly insufficient funds. Request will remain unprocessed until funds are available", "err", run.AllErrors.ToError(), "max gas price", maxGasPriceWei, "reqID", req.req.RequestId, "juelsNeeded", juelsNeeded) return maxLink, run, payload, gaslimit, errors.Wrap(run.AllErrors.ToError(), "simulation errored") } - if len(trrs.FinalResult(lsn.l).Values) != 1 { - lsn.l.Errorw("Unexpected number of outputs", "expectedNumOutputs", 1, "actualNumOutputs", len(trrs.FinalResult(lsn.l).Values)) + if len(trrs.FinalResult(lg).Values) != 1 { + lg.Errorw("Unexpected number of outputs", "expectedNumOutputs", 1, "actualNumOutputs", len(trrs.FinalResult(lg).Values)) return maxLink, run, payload, gaslimit, errors.New("unexpected number of outputs") } // Run succeeded, we expect a byte array representing the billing amount - b, ok := trrs.FinalResult(lsn.l).Values[0].([]uint8) + b, ok := trrs.FinalResult(lg).Values[0].([]uint8) if !ok { - lsn.l.Errorw("Unexpected type, expected []uint8 final result") + lg.Errorw("Unexpected type, expected []uint8 final result") return maxLink, run, payload, gaslimit, errors.New("expected []uint8 final result") } maxLink = utils.HexToBig(hexutil.Encode(b)[2:]) @@ -573,6 +599,7 @@ func (lsn *listenerV2) getConfirmedAt(req *vrf_coordinator_v2.VRFCoordinatorV2Ra "blockHash", req.Raw.BlockHash, "reqID", req.RequestId.String(), "newConfs", newConfs) + incDupeReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v2) } return req.Raw.BlockNumber + newConfs } @@ -641,9 +668,15 @@ func (lsn *listenerV2) Close() error { } func (lsn *listenerV2) HandleLog(lb log.Broadcast) { + if !lsn.deduper.shouldDeliver(lb.RawLog()) { + lsn.l.Tracew("skipping duplicate log broadcast", "log", lb.RawLog()) + return + } + wasOverCapacity := lsn.reqLogs.Deliver(lb) if wasOverCapacity { lsn.l.Error("Log mailbox is over capacity - dropped the oldest log") + incDroppedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, v2, reasonMailboxSize) } } @@ -652,12 +685,20 @@ func (lsn *listenerV2) JobID() int32 { return lsn.job.ID } -func toRequestSet(reqs []pendingRequest) map[string]struct{} { +func (lsn *listenerV2) fromAddresses() []common.Address { + var addresses []common.Address + for _, a := range lsn.job.VRFSpec.FromAddresses { + addresses = append(addresses, a.Address()) + } + return addresses +} + +func uniqueReqs(reqs []pendingRequest) int { s := map[string]struct{}{} for _, r := range reqs { s[r.req.RequestId.String()] = struct{}{} } - return s + return len(s) } // GasProofVerification is an upper limit on the gas used for verifying the VRF proof on-chain. diff --git a/core/services/vrf/listener_v2_test.go b/core/services/vrf/listener_v2_test.go index a86e5df06c8..9e89edc3d0c 100644 --- a/core/services/vrf/listener_v2_test.go +++ b/core/services/vrf/listener_v2_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/sqlx" ) -func addEthTx(t *testing.T, db *sqlx.DB, from common.Address, state bulletprooftxmanager.EthTxState, maxLink string, subID uint64) { +func addEthTx(t *testing.T, db *sqlx.DB, from common.Address, state txmgr.EthTxState, maxLink string, subID uint64) { _, err := db.Exec(`INSERT INTO eth_txes (from_address, to_address, encoded_payload, value, gas_limit, state, created_at, meta, subject, evm_chain_id, min_confirmations, pipeline_task_run_id) VALUES ( $1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9, $10, $11 @@ -33,7 +33,7 @@ func addEthTx(t *testing.T, db *sqlx.DB, from common.Address, state bulletprooft 0, // value 0, // limit state, - bulletprooftxmanager.EthTxMeta{ + txmgr.EthTxMeta{ MaxLink: maxLink, SubID: subID, }, @@ -56,7 +56,7 @@ func addConfirmedEthTx(t *testing.T, db *sqlx.DB, from common.Address, maxLink s []byte(`blah`), // payload 0, // value 0, // limit - bulletprooftxmanager.EthTxMeta{ + txmgr.EthTxMeta{ MaxLink: maxLink, SubID: subID, }, @@ -87,28 +87,28 @@ func TestMaybeSubtractReservedLink(t *testing.T) { subID := uint64(1) // Insert an unstarted eth tx with link metadata - addEthTx(t, db, k.Address.Address(), bulletprooftxmanager.EthTxUnstarted, "10000", subID) - start, err := MaybeSubtractReservedLink(lggr, q, k.Address.Address(), big.NewInt(100_000), chainID, subID) + addEthTx(t, db, k.Address.Address(), txmgr.EthTxUnstarted, "10000", subID) + start, err := MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // A confirmed tx should not affect the starting balance addConfirmedEthTx(t, db, k.Address.Address(), "10000", subID, 1) - start, err = MaybeSubtractReservedLink(lggr, q, k.Address.Address(), big.NewInt(100_000), chainID, subID) + start, err = MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // An unconfirmed tx _should_ affect the starting balance. - addEthTx(t, db, k.Address.Address(), bulletprooftxmanager.EthTxUnstarted, "10000", subID) - start, err = MaybeSubtractReservedLink(lggr, q, k.Address.Address(), big.NewInt(100_000), chainID, subID) + addEthTx(t, db, k.Address.Address(), txmgr.EthTxUnstarted, "10000", subID) + start, err = MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) assert.Equal(t, "80000", start.String()) // One subscriber's reserved link should not affect other subscribers prospective balance. otherSubID := uint64(2) require.NoError(t, err) - addEthTx(t, db, k.Address.Address(), bulletprooftxmanager.EthTxUnstarted, "10000", otherSubID) - start, err = MaybeSubtractReservedLink(lggr, q, k.Address.Address(), big.NewInt(100_000), chainID, subID) + addEthTx(t, db, k.Address.Address(), txmgr.EthTxUnstarted, "10000", otherSubID) + start, err = MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) require.Equal(t, "80000", start.String()) @@ -117,15 +117,15 @@ func TestMaybeSubtractReservedLink(t *testing.T) { require.NoError(t, err) anotherSubID := uint64(3) - addEthTx(t, db, k2.Address.Address(), bulletprooftxmanager.EthTxUnstarted, "10000", anotherSubID) - start, err = MaybeSubtractReservedLink(lggr, q, k.Address.Address(), big.NewInt(100_000), chainID, subID) + addEthTx(t, db, k2.Address.Address(), txmgr.EthTxUnstarted, "10000", anotherSubID) + start, err = MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) require.Equal(t, "80000", start.String()) // A subscriber's balance is deducted with the link reserved across multiple keys, // i.e, gas lanes. - addEthTx(t, db, k2.Address.Address(), bulletprooftxmanager.EthTxUnstarted, "10000", subID) - start, err = MaybeSubtractReservedLink(lggr, q, k2.Address.Address(), big.NewInt(100_000), chainID, subID) + addEthTx(t, db, k2.Address.Address(), txmgr.EthTxUnstarted, "10000", subID) + start, err = MaybeSubtractReservedLink(lggr, q, big.NewInt(100_000), chainID, subID) require.NoError(t, err) require.Equal(t, "70000", start.String()) } diff --git a/core/services/vrf/log_dedupe.go b/core/services/vrf/log_dedupe.go new file mode 100644 index 00000000000..194c1dc031e --- /dev/null +++ b/core/services/vrf/log_dedupe.go @@ -0,0 +1,74 @@ +package vrf + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// pruneInterval is the interval in blocks at which to prune old data from the delivered set. +const pruneInterval = 100 + +func newLogDeduper(lookback int) *logDeduper { + return &logDeduper{ + delivered: make(map[logKey]struct{}), + lookback: lookback, + } +} + +// logDeduper prevents duplicate logs from being reprocessed. +type logDeduper struct { + + // delivered is the set of logs within the lookback that have already been delivered. + delivered map[logKey]struct{} + + // lookback defines how long state should be kept for. Logs included in blocks older than + // lookback may or may not be redelivered. + lookback int + + // lastPruneHeight is the blockheight at which logs were last pruned. + lastPruneHeight uint64 +} + +// logKey represents uniquely identifying information for a single log broadcast. +type logKey struct { + + // blockHash of the block the log was included in. + blockHash common.Hash + + // blockNumber of the block the log was included in. This is necessary to prune old logs. + blockNumber uint64 + + // logIndex of the log in the block. + logIndex uint +} + +func (l *logDeduper) shouldDeliver(log types.Log) bool { + defer l.prune(log.BlockNumber) + key := logKey{ + blockHash: log.BlockHash, + blockNumber: log.BlockNumber, + logIndex: log.Index, + } + + if _, ok := l.delivered[key]; ok { + return false + } + + l.delivered[key] = struct{}{} + return true +} + +func (l *logDeduper) prune(logBlock uint64) { + // Only prune every pruneInterval blocks + if int(logBlock)-int(l.lastPruneHeight) < pruneInterval { + return + } + + for key := range l.delivered { + if int(key.blockNumber) < int(logBlock)-l.lookback { + delete(l.delivered, key) + } + } + + l.lastPruneHeight = logBlock +} diff --git a/core/services/vrf/log_dedupe_test.go b/core/services/vrf/log_dedupe_test.go new file mode 100644 index 00000000000..e4b574074b2 --- /dev/null +++ b/core/services/vrf/log_dedupe_test.go @@ -0,0 +1,167 @@ +package vrf + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestLogDeduper(t *testing.T) { + tests := []struct { + name string + logs []types.Log + results []bool + }{ + { + name: "dupe", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + }, + results: []bool{true, false}, + }, + { + name: "same block number different hash", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x2}, + Index: 3, + }, + }, + results: []bool{true, true}, + }, + { + name: "same block number same hash different index", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 4, + }, + }, + results: []bool{true, true}, + }, + { + name: "same block number same hash different index", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 4, + }, + }, + results: []bool{true, true}, + }, + { + name: "multiple blocks with dupes", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 4, + }, + { + BlockNumber: 11, + BlockHash: common.Hash{0x11}, + Index: 0, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 3, + }, + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 4, + }, + { + BlockNumber: 12, + BlockHash: common.Hash{0x12}, + Index: 1, + }, + }, + results: []bool{true, true, true, false, false, true}, + }, + { + name: "prune", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 3, + }, + { + BlockNumber: 11, + BlockHash: common.Hash{0x11}, + Index: 11, + }, + { + BlockNumber: 115, + BlockHash: common.Hash{0x1, 0x1, 0x5}, + Index: 0, + }, + // Now the logs at blocks 10 and 11 should be pruned, and therefor redelivered. + // The log at block 115 should not be redelivered. + { + BlockNumber: 10, + BlockHash: common.Hash{0x10}, + Index: 3, + }, + { + BlockNumber: 11, + BlockHash: common.Hash{0x11}, + Index: 11, + }, + { + BlockNumber: 115, + BlockHash: common.Hash{0x1, 0x1, 0x5}, + Index: 0, + }, + }, + results: []bool{true, true, true, true, true, false}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + deduper := newLogDeduper(100) + + for i := range test.logs { + require.Equal(t, test.results[i], deduper.shouldDeliver(test.logs[i]), + "expected shouldDeliver for log %d to be %t", i, test.results[i]) + } + }) + } +} diff --git a/core/services/vrf/metrics.go b/core/services/vrf/metrics.go new file mode 100644 index 00000000000..410e1ac7f4f --- /dev/null +++ b/core/services/vrf/metrics.go @@ -0,0 +1,67 @@ +package vrf + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + uuid "github.com/satori/go.uuid" +) + +// version describes a VRF version. +type version string + +const ( + v1 version = "v1" + v2 version = "v2" +) + +// dropReason describes a reason why a VRF request is dropped from the queue. +type dropReason string + +const ( + // reasonMailboxSize describes when a VRF request is dropped due to the log mailbox being + // over capacity. + reasonMailboxSize dropReason = "mailbox_size" + + // reasonAge describes when a VRF request is dropped due to its age. + reasonAge dropReason = "age" +) + +var ( + metricQueueSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "vrf_request_queue_size", + Help: "The number of VRF requests currently in the in-memory queue.", + }, []string{"job_name", "external_job_id", "vrf_version"}) + + metricProcessedReqs = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "vrf_processed_request_count", + Help: "The number of VRF requests processed.", + }, []string{"job_name", "external_job_id", "vrf_version"}) + + metricDroppedRequests = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "vrf_dropped_request_count", + Help: "The number of VRF requests dropped due to reasons such as expiry or mailbox size.", + }, []string{"job_name", "external_job_id", "vrf_version", "drop_reason"}) + + metricDupeRequests = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "vrf_duplicate_requests", + Help: "The number of times the VRF listener receives duplicate requests, which could indicate a reorg.", + }, []string{"job_name", "external_job_id", "vrf_version"}) +) + +func updateQueueSize(jobName string, extJobID uuid.UUID, vrfVersion version, size int) { + metricQueueSize.WithLabelValues(jobName, extJobID.String(), string(vrfVersion)). + Set(float64(size)) +} + +func incProcessedReqs(jobName string, extJobID uuid.UUID, vrfVersion version) { + metricProcessedReqs.WithLabelValues(jobName, extJobID.String(), string(vrfVersion)).Inc() +} + +func incDroppedReqs(jobName string, extJobID uuid.UUID, vrfVersion version, reason dropReason) { + metricDroppedRequests.WithLabelValues( + jobName, extJobID.String(), string(vrfVersion), string(reason)).Inc() +} + +func incDupeReqs(jobName string, extJobID uuid.UUID, vrfVersion version) { + metricDupeRequests.WithLabelValues(jobName, extJobID.String(), string(vrfVersion)).Inc() +} diff --git a/core/services/vrf/validate_test.go b/core/services/vrf/validate_test.go index 1ac1bd7c6b4..2a8c7646916 100644 --- a/core/services/vrf/validate_test.go +++ b/core/services/vrf/validate_test.go @@ -25,6 +25,7 @@ schemaVersion = 1 minIncomingConfirmations = 10 publicKey = "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F8179800" coordinatorAddress = "0xB3b7874F13387D44a3398D298B075B7A3505D8d4" +requestTimeout = "168h" # 7 days observationSource = """ decode_log [type=ethabidecodelog abi="RandomnessRequest(bytes32 keyHash,uint256 seed,bytes32 indexed jobID,address sender,uint256 fee,bytes32 requestID)" @@ -50,6 +51,7 @@ decode_log->vrf->encode_tx->submit_tx assert.Equal(t, uint32(10), s.VRFSpec.MinIncomingConfirmations) assert.Equal(t, "0xB3b7874F13387D44a3398D298B075B7A3505D8d4", s.VRFSpec.CoordinatorAddress.String()) assert.Equal(t, "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800", s.VRFSpec.PublicKey.String()) + require.Equal(t, 168*time.Hour, s.VRFSpec.RequestTimeout) }, }, { @@ -80,7 +82,7 @@ decode_log->vrf->encode_tx->submit_tx `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) - require.True(t, ErrKeyNotSet == errors.Cause(err)) + require.True(t, errors.Is(ErrKeyNotSet, errors.Cause(err))) }, }, { @@ -111,7 +113,7 @@ decode_log->vrf->encode_tx->submit_tx `, assertion: func(t *testing.T, s job.Job, err error) { require.Error(t, err) - require.True(t, ErrKeyNotSet == errors.Cause(err)) + require.True(t, errors.Is(ErrKeyNotSet, errors.Cause(err))) }, }, { diff --git a/core/services/vrf/vrf_coordinator_solidity_crosscheck_test.go b/core/services/vrf/vrf_coordinator_solidity_crosscheck_test.go index e0860d4dcd7..a9a83d0808c 100644 --- a/core/services/vrf/vrf_coordinator_solidity_crosscheck_test.go +++ b/core/services/vrf/vrf_coordinator_solidity_crosscheck_test.go @@ -95,22 +95,29 @@ func newVRFCoordinatorUniverseWithV08Consumer(t *testing.T, key ethkey.KeyV2) co // newVRFCoordinatorUniverse sets up all identities and contracts associated with // testing the solidity VRF contracts involved in randomness request workflow -func newVRFCoordinatorUniverse(t *testing.T, key ethkey.KeyV2) coordinatorUniverse { - oracleTransactor := cltest.MustNewSimulatedBackendKeyedTransactor(t, key.ToEcdsaPrivKey()) +func newVRFCoordinatorUniverse(t *testing.T, keys ...ethkey.KeyV2) coordinatorUniverse { + var oracleTransactors []*bind.TransactOpts + for _, key := range keys { + oracleTransactors = append(oracleTransactors, cltest.MustNewSimulatedBackendKeyedTransactor(t, key.ToEcdsaPrivKey())) + } + var ( - sergey = newIdentity(t) - neil = newIdentity(t) - ned = newIdentity(t) - carol = newIdentity(t) - nallory = oracleTransactor + sergey = newIdentity(t) + neil = newIdentity(t) + ned = newIdentity(t) + carol = newIdentity(t) ) genesisData := core.GenesisAlloc{ - sergey.From: {Balance: assets.Ether(1000)}, - neil.From: {Balance: assets.Ether(1000)}, - ned.From: {Balance: assets.Ether(1000)}, - carol.From: {Balance: assets.Ether(1000)}, - nallory.From: {Balance: assets.Ether(1000)}, + sergey.From: {Balance: assets.Ether(1000)}, + neil.From: {Balance: assets.Ether(1000)}, + ned.From: {Balance: assets.Ether(1000)}, + carol.From: {Balance: assets.Ether(1000)}, } + + for _, t := range oracleTransactors { + genesisData[t.From] = core.GenesisAccount{Balance: assets.Ether(1000)} + } + gasLimit := ethconfig.Defaults.Miner.GasCeil consumerABI, err := abi.JSON(strings.NewReader( solidity_vrf_consumer_interface.VRFConsumerABI)) diff --git a/core/services/vrf/vrf_hash_to_curve_cost_test.go b/core/services/vrf/vrf_hash_to_curve_cost_test.go index 6a2c8690ea3..b9ba949e2b3 100644 --- a/core/services/vrf/vrf_hash_to_curve_cost_test.go +++ b/core/services/vrf/vrf_hash_to_curve_cost_test.go @@ -1,12 +1,13 @@ package vrf_test import ( - "context" "crypto/ecdsa" "math/big" "strings" "testing" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/smartcontractkit/chainlink/core/assets" @@ -66,7 +67,7 @@ func estimateGas(t *testing.T, backend *backends.SimulatedBackend, require.NoError(t, err, "failed to construct raw %s transaction with args %s", method, args) callMsg := ethereum.CallMsg{From: from, To: &to, Data: rawData} - estimate, err := backend.EstimateGas(context.TODO(), callMsg) + estimate, err := backend.EstimateGas(testutils.Context(t), callMsg) require.NoError(t, err, "failed to estimate gas from %s call with args %s", method, args) return estimate diff --git a/core/services/webhook/delegate.go b/core/services/webhook/delegate.go index fe21cb989ed..6703485623d 100644 --- a/core/services/webhook/delegate.go +++ b/core/services/webhook/delegate.go @@ -65,7 +65,8 @@ func (d *Delegate) BeforeJobDeleted(jb job.Job) { } } -func (d *Delegate) ServicesForSpec(spec job.Job) ([]job.Service, error) { +// ServicesForSpec satisfies the job.Delegate interface. +func (d *Delegate) ServicesForSpec(spec job.Job) ([]job.ServiceCtx, error) { // TODO: we need to fill these out manually, find a better fix if spec.PipelineSpec == nil { spec.PipelineSpec = &pipeline.Spec{} @@ -77,7 +78,7 @@ func (d *Delegate) ServicesForSpec(spec job.Job) ([]job.Service, error) { spec: spec, webhookJobRunner: d.webhookJobRunner, } - return []job.Service{service}, nil + return []job.ServiceCtx{service}, nil } type pseudoService struct { @@ -85,7 +86,8 @@ type pseudoService struct { webhookJobRunner *webhookJobRunner } -func (s pseudoService) Start() error { +// Start starts PseudoService. +func (s pseudoService) Start(context.Context) error { // add the spec to the webhookJobRunner return s.webhookJobRunner.addSpec(s.spec) } @@ -157,7 +159,7 @@ func (r *webhookJobRunner) RunJob(ctx context.Context, jobUUID uuid.UUID, reques "uuid", spec.ExternalJobID, ) - ctx, cancel := utils.CombinedContext(ctx, spec.chRemove) + ctx, cancel := utils.WithCloseChan(ctx, spec.chRemove) defer cancel() vars := pipeline.NewVarsFrom(map[string]interface{}{ diff --git a/core/services/webhook/delegate_test.go b/core/services/webhook/delegate_test.go index c88e34d52b0..e440eeaa76a 100644 --- a/core/services/webhook/delegate_test.go +++ b/core/services/webhook/delegate_test.go @@ -9,14 +9,16 @@ import ( "github.com/pkg/errors" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/pipeline" pipelinemocks "github.com/smartcontractkit/chainlink/core/services/pipeline/mocks" "github.com/smartcontractkit/chainlink/core/services/webhook" webhookmocks "github.com/smartcontractkit/chainlink/core/services/webhook/mocks" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) func TestWebhookDelegate(t *testing.T) { @@ -60,7 +62,7 @@ func TestWebhookDelegate(t *testing.T) { require.Equal(t, webhook.ErrJobNotExists, errors.Cause(err)) // Should succeed after service is started upon a successful run - err = service.Start() + err = service.Start(testutils.Context(t)) require.NoError(t, err) runner.On("Run", mock.Anything, mock.AnythingOfType("*pipeline.Run"), mock.Anything, mock.Anything, mock.Anything). diff --git a/core/shutdown/shutdown.go b/core/shutdown/shutdown.go index 1861e87f85c..1a864d3a4d8 100644 --- a/core/shutdown/shutdown.go +++ b/core/shutdown/shutdown.go @@ -7,10 +7,10 @@ import ( ) // HandleShutdown waits for SIGINT/SIGTERM signals and calls handleFunc -func HandleShutdown(handleFunc func()) { +func HandleShutdown(handleFunc func(sig string)) { ch := make(chan os.Signal, 1) ossignal.Notify(ch, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-ch - handleFunc() + sig := <-ch + handleFunc(sig.String()) } diff --git a/core/shutdown/shutdown_test.go b/core/shutdown/shutdown_test.go index 57d4f155e27..ecbeae76789 100644 --- a/core/shutdown/shutdown_test.go +++ b/core/shutdown/shutdown_test.go @@ -22,7 +22,9 @@ func TestHandleShutdown(t *testing.T) { for name, sig := range tests { t.Run(name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - go HandleShutdown(cancel) + go HandleShutdown(func(string) { + cancel() + }) // have to wait for ossignal.Notify time.Sleep(time.Second) diff --git a/core/static/static.go b/core/static/static.go index f64cb8d81e5..7b640390417 100644 --- a/core/static/static.go +++ b/core/static/static.go @@ -2,7 +2,9 @@ package static import ( "fmt" + "log" "net/url" + "os" "time" "github.com/Masterminds/semver/v3" @@ -36,7 +38,10 @@ func init() { func checkVersion() { if Version == "unset" { - return + if os.Getenv("CHAINLINK_DEV") == "true" { + return + } + log.Println(`Version was unset but CHAINLINK_DEV was not set to "true". Chainlink should be built with static.Version set to a valid semver for production builds.`) } else if _, err := semver.NewVersion(Version); err != nil { panic(fmt.Sprintf("Version invalid: %q is not valid semver", Version)) } diff --git a/core/store/migrate/migrate_test.go b/core/store/migrate/migrate_test.go index 1127c63b6e1..145bd7846be 100644 --- a/core/store/migrate/migrate_test.go +++ b/core/store/migrate/migrate_test.go @@ -2,24 +2,68 @@ package migrate_test import ( "testing" + "time" "github.com/lib/pq" "github.com/pressly/goose/v3" uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/pipeline" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" + relaytypes "github.com/smartcontractkit/chainlink/core/services/relay/types" + "github.com/smartcontractkit/chainlink/core/store/models" ) +var migrationDir = "migrations" + +type OffchainReporting2OracleSpec100 struct { + ID int32 `toml:"-"` + ContractID string `toml:"contractID"` + Relay relaytypes.Network `toml:"relay"` + RelayConfig job.JSONConfig `toml:"relayConfig"` + P2PBootstrapPeers pq.StringArray `toml:"p2pBootstrapPeers"` + OCRKeyBundleID null.String `toml:"ocrKeyBundleID"` + MonitoringEndpoint null.String `toml:"monitoringEndpoint"` + TransmitterID null.String `toml:"transmitterID"` + BlockchainTimeout models.Interval `toml:"blockchainTimeout"` + ContractConfigTrackerPollInterval models.Interval `toml:"contractConfigTrackerPollInterval"` + ContractConfigConfirmations uint16 `toml:"contractConfigConfirmations"` + JuelsPerFeeCoinPipeline string `toml:"juelsPerFeeCoinSource"` + CreatedAt time.Time `toml:"-"` + UpdatedAt time.Time `toml:"-"` +} + +func getOCR2Spec100() OffchainReporting2OracleSpec100 { + return OffchainReporting2OracleSpec100{ + ID: 100, + ContractID: "terra_187246hr3781h9fd198fh391g8f924", + Relay: "terra", + RelayConfig: map[string]interface{}{"chainID": float64(1337)}, + P2PBootstrapPeers: pq.StringArray{""}, + OCRKeyBundleID: null.String{}, + MonitoringEndpoint: null.StringFrom("endpoint:chainlink.monitor"), + TransmitterID: null.String{}, + BlockchainTimeout: 1337, + ContractConfigTrackerPollInterval: 16, + ContractConfigConfirmations: 32, + JuelsPerFeeCoinPipeline: `ds1 [type=bridge name=voter_turnout]; + ds1_parse [type=jsonparse path="one,two"]; + ds1_multiply [type=multiply times=1.23]; + ds1 -> ds1_parse -> ds1_multiply -> answer1; + answer1 [type=median index=0];`, + } +} + func TestMigrate_0100_BootstrapConfigs(t *testing.T) { - _, db := heavyweight.FullTestDB(t, "migrations", false, false) + _, db := heavyweight.FullTestDB(t, migrationDir, false, false) lggr := logger.TestLogger(t) cfg := configtest.NewTestGeneralConfig(t) - err := goose.UpTo(db.DB, "migrations", 99) + err := goose.UpTo(db.DB, migrationDir, 99) require.NoError(t, err) pipelineORM := pipeline.NewORM(db, lggr, cfg) @@ -31,43 +75,31 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { require.NoError(t, err) newFormatBoostrapPipelineID2, err := pipelineORM.CreateSpec(pipeline.Pipeline{}, 0) require.NoError(t, err) - jobORM := job.NewORM(db, nil, pipelineORM, nil, lggr, cfg) // OCR2 struct at migration v0099 type OffchainReporting2OracleSpec struct { - job.OffchainReporting2OracleSpec + OffchainReporting2OracleSpec100 IsBootstrapPeer bool } // Job struct at migration v0099 type Job struct { job.Job - Offchainreporting2OracleSpec *OffchainReporting2OracleSpec + OffchainreportingOracleSpecID *int32 + Offchainreporting2OracleSpecID *int32 + Offchainreporting2OracleSpec *OffchainReporting2OracleSpec } spec := OffchainReporting2OracleSpec{ - OffchainReporting2OracleSpec: job.OffchainReporting2OracleSpec{ - ID: 100, - ContractID: "terra_187246hr3781h9fd198fh391g8f924", - Relay: "terra", - RelayConfig: map[string]interface{}{"chainID": float64(1337)}, - P2PBootstrapPeers: pq.StringArray{""}, - OCRKeyBundleID: null.String{}, - MonitoringEndpoint: null.StringFrom("endpoint:chainlink.monitor"), - TransmitterID: null.String{}, - BlockchainTimeout: 1337, - ContractConfigTrackerPollInterval: 16, - ContractConfigConfirmations: 32, - JuelsPerFeeCoinPipeline: "", - }, - IsBootstrapPeer: true, + OffchainReporting2OracleSpec100: getOCR2Spec100(), + IsBootstrapPeer: true, } spec2 := OffchainReporting2OracleSpec{ - OffchainReporting2OracleSpec: job.OffchainReporting2OracleSpec{ + OffchainReporting2OracleSpec100: OffchainReporting2OracleSpec100{ ID: 200, ContractID: "sol_187246hr3781h9fd198fh391g8f924", Relay: "sol", - RelayConfig: job.RelayConfig{}, + RelayConfig: job.JSONConfig{}, P2PBootstrapPeers: pq.StringArray{""}, OCRKeyBundleID: null.String{}, MonitoringEndpoint: null.StringFrom("endpoint:chain.link.monitor"), @@ -82,30 +114,30 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { jb := Job{ Job: job.Job{ - ID: 10, - ExternalJobID: uuid.NewV4(), - Type: job.OffchainReporting2, - SchemaVersion: 1, - PipelineSpecID: pipelineID, - Offchainreporting2OracleSpecID: &spec.ID, + ID: 10, + ExternalJobID: uuid.NewV4(), + Type: job.OffchainReporting2, + SchemaVersion: 1, + PipelineSpecID: pipelineID, }, - Offchainreporting2OracleSpec: &spec, + Offchainreporting2OracleSpec: &spec, + Offchainreporting2OracleSpecID: &spec.ID, } jb2 := Job{ Job: job.Job{ - ID: 20, - ExternalJobID: uuid.NewV4(), - Type: job.OffchainReporting2, - SchemaVersion: 1, - PipelineSpecID: pipelineID2, - Offchainreporting2OracleSpecID: &spec2.ID, + ID: 20, + ExternalJobID: uuid.NewV4(), + Type: job.OffchainReporting2, + SchemaVersion: 1, + PipelineSpecID: pipelineID2, }, - Offchainreporting2OracleSpec: &spec2, + Offchainreporting2OracleSpec: &spec2, + Offchainreporting2OracleSpecID: &spec2.ID, } nonBootstrapSpec := OffchainReporting2OracleSpec{ - OffchainReporting2OracleSpec: job.OffchainReporting2OracleSpec{ + OffchainReporting2OracleSpec100: OffchainReporting2OracleSpec100{ ID: 101, P2PBootstrapPeers: pq.StringArray{""}, ContractID: "empty", @@ -114,21 +146,21 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { } nonBootstrapJob := Job{ Job: job.Job{ - ID: 11, - ExternalJobID: uuid.NewV4(), - Type: job.OffchainReporting2, - SchemaVersion: 1, - PipelineSpecID: nonBootstrapPipelineID, - Offchainreporting2OracleSpecID: &nonBootstrapSpec.ID, + ID: 11, + ExternalJobID: uuid.NewV4(), + Type: job.OffchainReporting2, + SchemaVersion: 1, + PipelineSpecID: nonBootstrapPipelineID, }, - Offchainreporting2OracleSpec: &nonBootstrapSpec, + Offchainreporting2OracleSpec: &nonBootstrapSpec, + Offchainreporting2OracleSpecID: &nonBootstrapSpec.ID, } newFormatBoostrapSpec := job.BootstrapSpec{ ID: 1, ContractID: "evm_187246hr3781h9fd198fh391g8f924", Relay: "evm", - RelayConfig: job.RelayConfig{}, + RelayConfig: job.JSONConfig{}, MonitoringEndpoint: null.StringFrom("new:chain.link.monitor"), BlockchainTimeout: 2448, ContractConfigTrackerPollInterval: 18, @@ -185,7 +217,7 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { require.NoError(t, err) // Migrate up - err = goose.UpByOne(db.DB, "migrations") + err = goose.UpByOne(db.DB, migrationDir) require.NoError(t, err) var bootstrapSpecs []job.BootstrapSpec @@ -198,10 +230,12 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { t.Logf("bootstrap id: %d\n", bootstrapSpec.ID) } - var jobs []job.Job - jobs, count, err := jobORM.FindJobs(0, 1000) + var jobs []Job + sql = `SELECT * FROM jobs ORDER BY created_at DESC, id DESC;` + err = db.Select(&jobs, sql) + require.NoError(t, err) - require.Equal(t, 4, count) + require.Len(t, jobs, 4) t.Logf("jobs count %d\n", len(jobs)) for _, jb := range jobs { t.Logf("job id: %d with BootstrapSpecID: %d\n", jb.ID, jb.BootstrapSpecID) @@ -211,6 +245,12 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { migratedJob := jobs[3] require.Nil(t, migratedJob.Offchainreporting2OracleSpecID) require.NotNil(t, migratedJob.BootstrapSpecID) + + var resultingBootstrapSpec job.BootstrapSpec + err = db.Get(&resultingBootstrapSpec, `SELECT * FROM bootstrap_specs WHERE id = $1`, *migratedJob.BootstrapSpecID) + migratedJob.BootstrapSpec = &resultingBootstrapSpec + require.NoError(t, err) + require.Equal(t, &job.BootstrapSpec{ ID: 2, ContractID: spec.ContractID, @@ -226,12 +266,13 @@ func TestMigrate_0100_BootstrapConfigs(t *testing.T) { require.Equal(t, job.Bootstrap, migratedJob.Type) sql = `SELECT COUNT(*) FROM offchainreporting2_oracle_specs;` + var count int err = db.Get(&count, sql) require.NoError(t, err) require.Equal(t, 1, count) // Migrate down - err = goose.Down(db.DB, "migrations") + err = goose.Down(db.DB, migrationDir) require.NoError(t, err) var oldJobs []Job @@ -287,3 +328,51 @@ ON jobs.offchainreporting2_oracle_spec_id = ocr2.id` require.Equal(t, jobIdAndContractId{ID: 20, ContractID: "sol_187246hr3781h9fd198fh391g8f924"}, jobsAndContracts[3]) } + +func TestMigrate_101_GenericOCR2(t *testing.T) { + _, db := heavyweight.FullTestDB(t, migrationDir, false, false) + err := goose.UpTo(db.DB, migrationDir, 100) + require.NoError(t, err) + + sql := `INSERT INTO offchainreporting2_oracle_specs (id, contract_id, relay, relay_config, p2p_bootstrap_peers, ocr_key_bundle_id, transmitter_id, + blockchain_timeout, contract_config_tracker_poll_interval, contract_config_confirmations, juels_per_fee_coin_pipeline, + monitoring_endpoint, created_at, updated_at) + VALUES (:id, :contract_id, :relay, :relay_config, :p2p_bootstrap_peers, :ocr_key_bundle_id, :transmitter_id, + :blockchain_timeout, :contract_config_tracker_poll_interval, :contract_config_confirmations, :juels_per_fee_coin_pipeline, + :monitoring_endpoint, NOW(), NOW()) + RETURNING id;` + + spec := getOCR2Spec100() + + _, err = db.NamedExec(sql, spec) + require.NoError(t, err) + + err = goose.UpByOne(db.DB, migrationDir) + require.NoError(t, err) + + type PluginValues struct { + PluginType job.OCR2PluginType + PluginConfig job.JSONConfig + } + + var pluginValues PluginValues + + sql = `SELECT plugin_type, plugin_config FROM ocr2_oracle_specs` + err = db.Get(&pluginValues, sql) + require.NoError(t, err) + + require.Equal(t, job.Median, pluginValues.PluginType) + require.Equal(t, job.JSONConfig{"juelsPerFeeCoinSource": spec.JuelsPerFeeCoinPipeline}, pluginValues.PluginConfig) + + err = goose.Down(db.DB, migrationDir) + + sql = `SELECT plugin_type, plugin_config FROM offchainreporting2_oracle_specs` + err = db.Get(&pluginValues, sql) + require.Error(t, err) + + var juels string + sql = `SELECT juels_per_fee_coin_pipeline FROM offchainreporting2_oracle_specs` + err = db.Get(&juels, sql) + require.NoError(t, err) + require.Equal(t, spec.JuelsPerFeeCoinPipeline, juels) +} diff --git a/core/store/migrate/migrations/0101_generic_ocr2.sql b/core/store/migrate/migrations/0101_generic_ocr2.sql new file mode 100644 index 00000000000..c0d78025d7c --- /dev/null +++ b/core/store/migrate/migrations/0101_generic_ocr2.sql @@ -0,0 +1,145 @@ +-- +goose Up +-- +goose StatementBegin + +ALTER TABLE offchainreporting2_oracle_specs + ADD COLUMN plugin_config JSONB NOT NULL DEFAULT '{}', + ADD COLUMN plugin_type text NOT NULL default ''; + +-- migrate existing juels_per_fee_coin_pipeline settings to json format and set plugin_type to median as the only +-- plugins that are supported before this version are median plugins. +UPDATE offchainreporting2_oracle_specs +SET plugin_type = 'median', + plugin_config = jsonb_build_object('juelsPerFeeCoinSource', juels_per_fee_coin_pipeline); + +ALTER TABLE offchainreporting2_oracle_specs + DROP COLUMN juels_per_fee_coin_pipeline; + +-- rename OCR2 tables +ALTER TABLE jobs + RENAME COLUMN offchainreporting2_oracle_spec_id TO ocr2_oracle_spec_id; +ALTER TABLE offchainreporting2_oracle_specs + RENAME TO ocr2_oracle_specs; + +ALTER TABLE offchainreporting2_contract_configs + RENAME TO ocr2_contract_configs; +ALTER TABLE ocr2_contract_configs + RENAME COLUMN offchainreporting2_oracle_spec_id TO ocr2_oracle_spec_id; + +ALTER TABLE offchainreporting2_latest_round_requested + RENAME TO ocr2_latest_round_requested; +ALTER TABLE ocr2_latest_round_requested + RENAME COLUMN offchainreporting2_oracle_spec_id TO ocr2_oracle_spec_id; + +ALTER TABLE offchainreporting2_pending_transmissions + RENAME TO ocr2_pending_transmissions; +ALTER TABLE ocr2_pending_transmissions + RENAME COLUMN offchainreporting2_oracle_spec_id TO ocr2_oracle_spec_id; + +ALTER TABLE offchainreporting2_persistent_states + RENAME TO ocr2_persistent_states; +ALTER TABLE ocr2_persistent_states + RENAME COLUMN offchainreporting2_oracle_spec_id TO ocr2_oracle_spec_id; + +-- rename OCR tables +ALTER TABLE jobs + RENAME COLUMN offchainreporting_oracle_spec_id TO ocr_oracle_spec_id; +ALTER TABLE offchainreporting_oracle_specs + RENAME TO ocr_oracle_specs; + +ALTER TABLE offchainreporting_contract_configs + RENAME TO ocr_contract_configs; +ALTER TABLE ocr_contract_configs + RENAME COLUMN offchainreporting_oracle_spec_id TO ocr_oracle_spec_id; + +-- this table does not have offchainreporting_oracle_spec_id +ALTER TABLE offchainreporting_discoverer_announcements + RENAME TO ocr_discoverer_announcements; + +ALTER TABLE offchainreporting_latest_round_requested + RENAME TO ocr_latest_round_requested; +ALTER TABLE ocr_latest_round_requested + RENAME COLUMN offchainreporting_oracle_spec_id TO ocr_oracle_spec_id; + +ALTER TABLE offchainreporting_pending_transmissions + RENAME TO ocr_pending_transmissions; +ALTER TABLE ocr_pending_transmissions + RENAME COLUMN offchainreporting_oracle_spec_id TO ocr_oracle_spec_id; + +ALTER TABLE offchainreporting_persistent_states + RENAME TO ocr_persistent_states; +ALTER TABLE ocr_persistent_states + RENAME COLUMN offchainreporting_oracle_spec_id TO ocr_oracle_spec_id; + +-- +goose StatementEnd + + +-- +goose Down +-- +goose StatementBegin + +ALTER TABLE ocr2_oracle_specs + ADD COLUMN juels_per_fee_coin_pipeline text NOT NULL default ''; + +UPDATE ocr2_oracle_specs +SET juels_per_fee_coin_pipeline = plugin_config ->> 'juelsPerFeeCoinSource'; + +ALTER TABLE ocr2_oracle_specs + DROP COLUMN plugin_config, + DROP COLUMN plugin_type; + +-- rename OCR2 tables +ALTER TABLE jobs + RENAME COLUMN ocr2_oracle_spec_id TO offchainreporting2_oracle_spec_id; + +ALTER TABLE ocr2_oracle_specs + RENAME TO offchainreporting2_oracle_specs; + +ALTER TABLE ocr2_contract_configs + RENAME TO offchainreporting2_contract_configs; +ALTER TABLE offchainreporting2_contract_configs + RENAME COLUMN ocr2_oracle_spec_id TO offchainreporting2_oracle_spec_id; + +ALTER TABLE ocr2_latest_round_requested + RENAME TO offchainreporting2_latest_round_requested; +ALTER TABLE offchainreporting2_latest_round_requested + RENAME COLUMN ocr2_oracle_spec_id TO offchainreporting2_oracle_spec_id; + +ALTER TABLE ocr2_pending_transmissions + RENAME TO offchainreporting2_pending_transmissions; +ALTER TABLE offchainreporting2_pending_transmissions + RENAME COLUMN ocr2_oracle_spec_id TO offchainreporting2_oracle_spec_id; + +ALTER TABLE ocr2_persistent_states + RENAME TO offchainreporting2_persistent_states; +ALTER TABLE offchainreporting2_persistent_states + RENAME COLUMN ocr2_oracle_spec_id TO offchainreporting2_oracle_spec_id; + +-- rename OCR tables +ALTER TABLE jobs + RENAME COLUMN ocr_oracle_spec_id TO offchainreporting_oracle_spec_id; +ALTER TABLE ocr_oracle_specs + RENAME TO offchainreporting_oracle_specs; + +ALTER TABLE ocr_contract_configs + RENAME TO offchainreporting_contract_configs; +ALTER TABLE offchainreporting_contract_configs + RENAME COLUMN ocr_oracle_spec_id TO offchainreporting_oracle_spec_id; + +ALTER TABLE ocr_discoverer_announcements + RENAME TO offchainreporting_discoverer_announcements; + +ALTER TABLE ocr_latest_round_requested + RENAME TO offchainreporting_latest_round_requested; +ALTER TABLE offchainreporting_latest_round_requested + RENAME COLUMN ocr_oracle_spec_id TO offchainreporting_oracle_spec_id; + +ALTER TABLE ocr_pending_transmissions + RENAME TO offchainreporting_pending_transmissions; +ALTER TABLE offchainreporting_pending_transmissions + RENAME COLUMN ocr_oracle_spec_id TO offchainreporting_oracle_spec_id; + +ALTER TABLE ocr_persistent_states + RENAME TO offchainreporting_persistent_states; +ALTER TABLE offchainreporting_persistent_states + RENAME COLUMN ocr_oracle_spec_id TO offchainreporting_oracle_spec_id; + +-- +goose StatementEnd diff --git a/core/store/migrate/migrations/0102_add_log_broadcasts_block_num_chain_idx.sql b/core/store/migrate/migrations/0102_add_log_broadcasts_block_num_chain_idx.sql new file mode 100644 index 00000000000..e46b846db12 --- /dev/null +++ b/core/store/migrate/migrations/0102_add_log_broadcasts_block_num_chain_idx.sql @@ -0,0 +1,5 @@ +-- +goose Up +CREATE INDEX idx_log_broadcasts_block_number_evm_chain_id ON log_broadcasts (evm_chain_id, block_number); + +-- +goose Down +DROP INDEX IF EXISTS idx_log_broadcasts_block_number_evm_chain_id; diff --git a/core/store/migrate/migrations/0103_terra_msgs_type_url.sql b/core/store/migrate/migrations/0103_terra_msgs_type_url.sql new file mode 100644 index 00000000000..ed4e8ba8291 --- /dev/null +++ b/core/store/migrate/migrations/0103_terra_msgs_type_url.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE terra_msgs ADD COLUMN type text NOT NULL DEFAULT '/terra.wasm.v1beta1.MsgExecuteContract'; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE terra_msgs DROP COLUMN type; +-- +goose StatementEnd diff --git a/core/store/migrate/migrations/0104_terra_cascade_delete.sql b/core/store/migrate/migrations/0104_terra_cascade_delete.sql new file mode 100644 index 00000000000..7d3be9b1f61 --- /dev/null +++ b/core/store/migrate/migrations/0104_terra_cascade_delete.sql @@ -0,0 +1,25 @@ +-- +goose Up +ALTER TABLE terra_nodes +DROP CONSTRAINT terra_nodes_terra_chain_id_fkey; + +ALTER TABLE terra_nodes + ADD FOREIGN KEY (terra_chain_id) REFERENCES terra_chains(id) ON DELETE CASCADE; + +ALTER TABLE terra_msgs +DROP CONSTRAINT terra_msgs_terra_chain_id_fkey; + +ALTER TABLE terra_msgs + ADD FOREIGN KEY (terra_chain_id) REFERENCES terra_chains(id) ON DELETE CASCADE; + +--+goose Down +ALTER TABLE terra_nodes +DROP CONSTRAINT terra_nodes_terra_chain_id_fkey; + +ALTER TABLE terra_nodes + ADD FOREIGN KEY (terra_chain_id) REFERENCES terra_chains(id); + +ALTER TABLE terra_msgs +DROP CONSTRAINT terra_msgs_terra_chain_id_fkey; + +ALTER TABLE terra_msgs + ADD FOREIGN KEY (terra_chain_id) REFERENCES terra_chains(id); diff --git a/core/store/migrate/migrations/0105_create_forwarder_addresses.sql b/core/store/migrate/migrations/0105_create_forwarder_addresses.sql new file mode 100644 index 00000000000..1788c68501c --- /dev/null +++ b/core/store/migrate/migrations/0105_create_forwarder_addresses.sql @@ -0,0 +1,17 @@ +-- +goose Up +CREATE TABLE evm_forwarders ( + id BIGSERIAL PRIMARY KEY, + address bytea NOT NULL UNIQUE, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + evm_chain_id numeric(78,0) NOT NULL REFERENCES evm_chains(id) ON DELETE CASCADE, + CONSTRAINT chk_address_length CHECK ((octet_length(address) = 20)) +); + +CREATE INDEX idx_forwarders_evm_chain_id ON evm_forwarders(evm_chain_id); +CREATE INDEX idx_forwarders_evm_address ON evm_forwarders(address); +CREATE INDEX idx_forwarders_created_at ON evm_forwarders USING brin (created_at); +CREATE INDEX idx_forwarders_updated_at ON evm_forwarders USING brin (updated_at); + +-- +goose Down +DROP TABLE evm_forwarders; diff --git a/core/store/migrate/migrations/0106_evm_node_uniqueness.sql b/core/store/migrate/migrations/0106_evm_node_uniqueness.sql new file mode 100644 index 00000000000..61f23c81f7d --- /dev/null +++ b/core/store/migrate/migrations/0106_evm_node_uniqueness.sql @@ -0,0 +1,16 @@ +-- +goose Up +-- Delete sendonlys if they redundantly duplicate a primary +DELETE FROM + evm_nodes a + USING evm_nodes b +WHERE + a.http_url = b.http_url + AND a.id != b.id + AND a.send_only; + +CREATE UNIQUE INDEX idx_unique_ws_url ON evm_nodes (ws_url); +CREATE UNIQUE INDEX idx_unique_http_url ON evm_nodes (http_url); + +-- +goose Down +DROP INDEX idx_unique_ws_url; +DROP INDEX idx_unique_http_url; diff --git a/core/store/migrate/migrations/0107_vrf_multiple_from_addresses.sql b/core/store/migrate/migrations/0107_vrf_multiple_from_addresses.sql new file mode 100644 index 00000000000..56adae87342 --- /dev/null +++ b/core/store/migrate/migrations/0107_vrf_multiple_from_addresses.sql @@ -0,0 +1,15 @@ +-- +goose Up +ALTER TABLE vrf_specs ADD COLUMN from_addresses bytea[] DEFAULT '{}' NOT NULL ; + +UPDATE vrf_specs SET from_addresses = from_addresses || from_address +WHERE from_address IS NOT NULL; + +ALTER TABLE vrf_specs DROP COLUMN from_address; + +-- +goose Down +ALTER TABLE vrf_specs ADD COLUMN from_address bytea; + +UPDATE vrf_specs SET from_address = from_addresses[1] +WHERE array_length(from_addresses, 1) > 0; + +ALTER TABLE vrf_specs DROP COLUMN from_addresses; diff --git a/core/store/models/terra/common.go b/core/store/models/terra/common.go new file mode 100644 index 00000000000..dfdb2484de0 --- /dev/null +++ b/core/store/models/terra/common.go @@ -0,0 +1,12 @@ +package terra + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// SendRequest represents a request to transfer Terra coins. +type SendRequest struct { + DestinationAddress sdk.AccAddress `json:"address"` + FromAddress sdk.AccAddress `json:"from"` + Amount sdk.Dec `json:"amount"` + TerraChainID string `json:"terraChainID"` + AllowHigherAmounts bool `json:"allowHigherAmounts"` +} diff --git a/core/testdata/testspecs/v2_specs.go b/core/testdata/testspecs/v2_specs.go index e5ffca36bd6..02b7f1ce00f 100644 --- a/core/testdata/testspecs/v2_specs.go +++ b/core/testdata/testspecs/v2_specs.go @@ -174,6 +174,7 @@ chainID = 1337 ) type KeeperSpecParams struct { + Name string ContractAddress string FromAddress string EvmChainID int @@ -193,7 +194,7 @@ func GenerateKeeperSpec(params KeeperSpecParams) KeeperSpec { template := ` type = "keeper" schemaVersion = 3 -name = "example keeper spec" +name = "%s" contractAddress = "%s" fromAddress = "%s" evmChainID = %d @@ -233,7 +234,7 @@ encode_check_upkeep_tx -> check_upkeep_tx -> decode_check_upkeep_tx -> encode_pe ` return KeeperSpec{ KeeperSpecParams: params, - toml: fmt.Sprintf(template, params.ContractAddress, params.FromAddress, params.EvmChainID, params.MinIncomingConfirmations), + toml: fmt.Sprintf(template, params.Name, params.ContractAddress, params.FromAddress, params.EvmChainID, params.MinIncomingConfirmations), } } @@ -242,7 +243,7 @@ type VRFSpecParams struct { Name string CoordinatorAddress string MinIncomingConfirmations int - FromAddress string + FromAddresses []string PublicKey string ObservationSource string RequestedConfsDelay int @@ -300,6 +301,7 @@ encode_tx [type=ethabiencode submit_tx [type=ethtx to="%s" data="$(encode_tx)" minConfirmations="0" + from="$(jobSpec.from)" txMeta="{\\"requestTxHash\\": $(jobRun.logTxHash),\\"requestID\\": $(decode_log.requestID),\\"jobID\\": $(jobSpec.databaseID)}" transmitChecker="{\\"CheckerType\\": \\"vrf_v1\\", \\"VRFCoordinatorAddress\\": \\"%s\\"}"] decode_log->vrf->encode_tx->submit_tx @@ -348,8 +350,12 @@ observationSource = """ ` toml := fmt.Sprintf(template, jobID, name, coordinatorAddress, confirmations, params.RequestedConfsDelay, requestTimeout.String(), publicKey, observationSource) - if params.FromAddress != "" { - toml = toml + "\n" + fmt.Sprintf(`fromAddress = "%s"`, params.FromAddress) + if len(params.FromAddresses) != 0 { + var addresses []string + for _, address := range params.FromAddresses { + addresses = append(addresses, fmt.Sprintf("%q", address)) + } + toml = toml + "\n" + fmt.Sprintf(`fromAddresses = [%s]`, strings.Join(addresses, ", ")) } return VRFSpec{VRFSpecParams: VRFSpecParams{ diff --git a/core/utils/disk_stats.go b/core/utils/disk_stats.go new file mode 100644 index 00000000000..08cc44e2455 --- /dev/null +++ b/core/utils/disk_stats.go @@ -0,0 +1,27 @@ +package utils + +import "github.com/shirou/gopsutil/v3/disk" + +//go:generate mockery --name DiskStatsProvider --output ./mocks --case=underscore + +// DiskStatsProvider describes the abstraction to the `shirou/gopsutil/v3` for mocking purposes +type DiskStatsProvider interface { + AvailableSpace(path string) (FileSize, error) +} + +type provider struct{} + +// NewDiskStatsProvider returns a new `DiskStatsProvider` instance +func NewDiskStatsProvider() DiskStatsProvider { + return &provider{} +} + +// AvailableSpace returns the available/free disk space in the requested `path`. Returns an error if it fails to find the path. +func (p provider) AvailableSpace(path string) (FileSize, error) { + diskUsage, err := disk.Usage(path) + if err != nil { + return 0, err + } + + return FileSize(diskUsage.Free), nil +} diff --git a/core/utils/mocks/disk_stats_provider.go b/core/utils/mocks/disk_stats_provider.go new file mode 100644 index 00000000000..a0c45b037df --- /dev/null +++ b/core/utils/mocks/disk_stats_provider.go @@ -0,0 +1,34 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + utils "github.com/smartcontractkit/chainlink/core/utils" + mock "github.com/stretchr/testify/mock" +) + +// DiskStatsProvider is an autogenerated mock type for the DiskStatsProvider type +type DiskStatsProvider struct { + mock.Mock +} + +// AvailableSpace provides a mock function with given fields: path +func (_m *DiskStatsProvider) AvailableSpace(path string) (utils.FileSize, error) { + ret := _m.Called(path) + + var r0 utils.FileSize + if rf, ok := ret.Get(0).(func(string) utils.FileSize); ok { + r0 = rf(path) + } else { + r0 = ret.Get(0).(utils.FileSize) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(path) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/utils/utils.go b/core/utils/utils.go index 7f0d69e9087..d18d4cfe783 100644 --- a/core/utils/utils.go +++ b/core/utils/utils.go @@ -10,7 +10,6 @@ import ( "fmt" "math/big" mrand "math/rand" - "reflect" "sort" "strings" "sync" @@ -402,8 +401,29 @@ func WaitGroupChan(wg *sync.WaitGroup) <-chan struct{} { return chAwait } +// WithCloseChan wraps a context so that it is canceled if the passed in +// channel is closed. +// NOTE: Spins up a goroutine that exits on cancellation. +// REMEMBER TO CALL CANCEL OTHERWISE IT CAN LEAD TO MEMORY LEAKS +func WithCloseChan(parentCtx context.Context, chStop <-chan struct{}) (ctx context.Context, cancel context.CancelFunc) { + ctx, cancel = context.WithCancel(parentCtx) + + go func() { + select { + case <-chStop: + case <-ctx.Done(): + } + cancel() + }() + + return ctx, cancel +} + // ContextFromChan creates a context that finishes when the provided channel // receives or is closed. +// When channel closes, the ctx.Err() will always be context.Canceled +// NOTE: Spins up a goroutine that exits on cancellation. +// REMEMBER TO CALL CANCEL OTHERWISE IT CAN LEAD TO MEMORY LEAKS func ContextFromChan(chStop <-chan struct{}) (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) go func() { @@ -418,6 +438,8 @@ func ContextFromChan(chStop <-chan struct{}) (context.Context, context.CancelFun // ContextFromChanWithDeadline creates a context with a deadline that finishes when the provided channel // receives or is closed. +// NOTE: Spins up a goroutine that exits on cancellation. +// REMEMBER TO CALL CANCEL OTHERWISE IT CAN LEAD TO MEMORY LEAKS func ContextFromChanWithDeadline(chStop <-chan struct{}, timeout time.Duration) (context.Context, context.CancelFunc) { ctx, cancel := context.WithTimeout(context.Background(), timeout) go func() { @@ -430,49 +452,6 @@ func ContextFromChanWithDeadline(chStop <-chan struct{}, timeout time.Duration) return ctx, cancel } -// CombinedContext creates a context that finishes when any of the provided -// signals finish. A signal can be a `context.Context`, a `chan struct{}`, or -// a `time.Duration` (which is transformed into a `context.WithTimeout`). -func CombinedContext(signals ...interface{}) (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) - if len(signals) == 0 { - return ctx, cancel - } - signals = append(signals, ctx) - - var cases []reflect.SelectCase - var cancel2 context.CancelFunc - for _, signal := range signals { - var ch reflect.Value - - switch sig := signal.(type) { - case context.Context: - ch = reflect.ValueOf(sig.Done()) - case <-chan struct{}: - ch = reflect.ValueOf(sig) - case chan struct{}: - ch = reflect.ValueOf(sig) - case time.Duration: - var ctxTimeout context.Context - ctxTimeout, cancel2 = context.WithTimeout(ctx, sig) - ch = reflect.ValueOf(ctxTimeout.Done()) - default: - panic(fmt.Sprintf("utils.CombinedContext cannot accept a value of type %T, skipping", sig)) - } - cases = append(cases, reflect.SelectCase{Chan: ch, Dir: reflect.SelectRecv}) - } - - go func() { - defer cancel() - if cancel2 != nil { - defer cancel2() - } - _, _, _ = reflect.Select(cases) - }() - - return ctx, cancel -} - // DependentAwaiter contains Dependent funcs type DependentAwaiter interface { AwaitDependents() <-chan struct{} @@ -814,10 +793,13 @@ func EVMBytesToUint64(buf []byte) uint64 { return result } -var ( - // ErrNotStarted is returned if the service is not started. - ErrNotStarted = errors.New("Not started") -) +type errNotStarted struct { + state StartStopOnceState +} + +func (e *errNotStarted) Error() string { + return fmt.Sprintf("service is %q, not started", e.state) +} // StartStopOnce contains a StartStopOnceState integer type StartStopOnce struct { @@ -837,6 +819,23 @@ const ( StartStopOnce_Stopped ) +func (s StartStopOnceState) String() string { + switch s { + case StartStopOnce_Unstarted: + return "Unstarted" + case StartStopOnce_Started: + return "Started" + case StartStopOnce_Starting: + return "Starting" + case StartStopOnce_Stopping: + return "Stopping" + case StartStopOnce_Stopped: + return "Stopped" + default: + return fmt.Sprintf("unrecognized state: %d", s) + } +} + // StartOnce sets the state to Started func (once *StartStopOnce) StartOnce(name string, fn func() error) error { // SAFETY: We do this compare-and-swap outside of the lock so that @@ -874,7 +873,7 @@ func (once *StartStopOnce) StopOnce(name string, fn func() error) error { success := once.state.CAS(int32(StartStopOnce_Started), int32(StartStopOnce_Stopping)) if !success { - return errors.Errorf("%v has already stopped once", name) + return errors.Errorf("%v is unstarted or has already stopped once", name) } err := fn() @@ -926,29 +925,45 @@ func (once *StartStopOnce) IfNotStopped(f func()) (ok bool) { // Ready returns ErrNotStarted if the state is not started. func (once *StartStopOnce) Ready() error { - if once.State() == StartStopOnce_Started { + state := once.State() + if state == StartStopOnce_Started { return nil } - return ErrNotStarted + return &errNotStarted{state: state} } // Healthy returns ErrNotStarted if the state is not started. // Override this per-service with more specific implementations. func (once *StartStopOnce) Healthy() error { - if once.State() == StartStopOnce_Started { + state := once.State() + if state == StartStopOnce_Started { return nil } - return ErrNotStarted + return &errNotStarted{state: state} } // WithJitter adds +/- 10% to a duration func WithJitter(d time.Duration) time.Duration { // #nosec + if d == 0 { + return 0 + } jitter := mrand.Intn(int(d) / 5) jitter = jitter - (jitter / 2) return time.Duration(int(d) + jitter) } +// NewRedialBackoff is a standard backoff to use for redialling or reconnecting to +// unreachable network endpoints +func NewRedialBackoff() backoff.Backoff { + return backoff.Backoff{ + Min: 1 * time.Second, + Max: 15 * time.Second, + Jitter: true, + } + +} + // KeyedMutex allows to lock based on particular values type KeyedMutex struct { mutexes sync.Map diff --git a/core/utils/utils_test.go b/core/utils/utils_test.go index bfcdaa65ed2..6830f2c3f08 100644 --- a/core/utils/utils_test.go +++ b/core/utils/utils_test.go @@ -1,7 +1,6 @@ package utils_test import ( - "context" "strings" "sync" "testing" @@ -392,77 +391,6 @@ func TestEVMBytesToUint64(t *testing.T) { require.Equal(t, uint64(17), utils.EVMBytesToUint64([]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11})) } -func TestCombinedContext(t *testing.T) { - t.Parallel() - t.Run("cancels when an inner context is canceled", func(t *testing.T) { - innerCtx, innerCancel := context.WithCancel(context.Background()) - defer innerCancel() - - chStop := make(chan struct{}) - - ctx, cancel := utils.CombinedContext(innerCtx, chStop, 1*time.Hour) - defer cancel() - - innerCancel() - - select { - case <-ctx.Done(): - case <-time.After(5 * time.Second): - t.Fatal("context didn't cancel") - } - }) - - t.Run("cancels when a channel is closed", func(t *testing.T) { - innerCtx, innerCancel := context.WithCancel(context.Background()) - defer innerCancel() - - chStop := make(chan struct{}) - - ctx, cancel := utils.CombinedContext(innerCtx, chStop, 1*time.Hour) - defer cancel() - - close(chStop) - - select { - case <-ctx.Done(): - case <-time.After(5 * time.Second): - t.Fatal("context didn't cancel") - } - }) - - t.Run("cancels when a duration elapses", func(t *testing.T) { - innerCtx, innerCancel := context.WithCancel(context.Background()) - defer innerCancel() - - chStop := make(chan struct{}) - - ctx, cancel := utils.CombinedContext(innerCtx, chStop, 1*time.Second) - defer cancel() - - select { - case <-ctx.Done(): - case <-time.After(5 * time.Second): - t.Fatal("context didn't cancel") - } - }) - - t.Run("doesn't cancel if none of its children cancel", func(t *testing.T) { - innerCtx, innerCancel := context.WithCancel(context.Background()) - defer innerCancel() - - chStop := make(chan struct{}) - - ctx, cancel := utils.CombinedContext(innerCtx, chStop, 1*time.Hour) - defer cancel() - - select { - case <-ctx.Done(): - t.Fatal("context canceled") - case <-time.After(5 * time.Second): - } - }) -} - func Test_WithJitter(t *testing.T) { d := 10 * time.Second diff --git a/core/web/auth/auth.go b/core/web/auth/auth.go index a98f1bc989e..0821777e1d2 100644 --- a/core/web/auth/auth.go +++ b/core/web/auth/auth.go @@ -142,7 +142,7 @@ func Authenticate(store Authenticator, methods ...authMethod) gin.HandlerFunc { var err error for _, method := range methods { err = method(c, store) - if err != auth.ErrorAuthFailed { + if !errors.Is(err, auth.ErrorAuthFailed) { break } } diff --git a/core/web/bridge_types_controller.go b/core/web/bridge_types_controller.go index 0ee5d9355aa..a29c24241c4 100644 --- a/core/web/bridge_types_controller.go +++ b/core/web/bridge_types_controller.go @@ -25,7 +25,7 @@ func ValidateBridgeTypeNotExist(bt *bridges.BridgeTypeRequest, orm bridges.ORM) if err == nil { fe.Add(fmt.Sprintf("Bridge Type %v already exists", bt.Name)) } - if err != nil && err != sql.ErrNoRows { + if err != nil && !errors.Is(err, sql.ErrNoRows) { fe.Add(fmt.Sprintf("Error determining if bridge type %v already exists", bt.Name)) } return fe.CoerceEmptyToNil() @@ -38,7 +38,7 @@ func ValidateBridgeType(bt *bridges.BridgeTypeRequest, orm bridges.ORM) error { if len(bt.Name.String()) < 1 { fe.Add("No name specified") } - if _, err := bridges.NewTaskType(bt.Name.String()); err != nil { + if _, err := bridges.ParseBridgeName(bt.Name.String()); err != nil { fe.Merge(err) } u := bt.URL.String() @@ -117,7 +117,7 @@ func (btc *BridgeTypesController) Index(c *gin.Context, size, page, offset int) func (btc *BridgeTypesController) Show(c *gin.Context) { name := c.Param("BridgeName") - taskType, err := bridges.NewTaskType(name) + taskType, err := bridges.ParseBridgeName(name) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) return @@ -141,7 +141,7 @@ func (btc *BridgeTypesController) Update(c *gin.Context) { name := c.Param("BridgeName") btr := &bridges.BridgeTypeRequest{} - taskType, err := bridges.NewTaskType(name) + taskType, err := bridges.ParseBridgeName(name) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) return @@ -178,7 +178,7 @@ func (btc *BridgeTypesController) Update(c *gin.Context) { func (btc *BridgeTypesController) Destroy(c *gin.Context) { name := c.Param("BridgeName") - taskType, err := bridges.NewTaskType(name) + taskType, err := bridges.ParseBridgeName(name) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) return diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index 772850856d1..29ace518b3f 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -5,15 +5,17 @@ import ( "net/http" "testing" - "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + + "github.com/manyminds/api2go/jsonapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -121,7 +123,7 @@ func TestValidateBridgeNotExist(t *testing.T) { // Create a duplicate bt := bridges.BridgeType{} - bt.Name = bridges.MustNewTaskType("solargridreporting") + bt.Name = bridges.MustParseBridgeName("solargridreporting") bt.URL = cltest.WebURL(t, "https://denergy.eth") assert.NoError(t, orm.CreateBridgeType(&bt)) @@ -149,7 +151,7 @@ func TestBridgeTypesController_Index(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() bt, err := setupBridgeControllerIndex(t, app.BridgeORM()) @@ -193,7 +195,7 @@ func TestBridgeTypesController_Index(t *testing.T) { func setupBridgeControllerIndex(t testing.TB, orm bridges.ORM) ([]*bridges.BridgeType, error) { bt1 := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges1"), + Name: bridges.MustParseBridgeName("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } @@ -203,7 +205,7 @@ func setupBridgeControllerIndex(t testing.TB, orm bridges.ORM) ([]*bridges.Bridg } bt2 := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges2"), + Name: bridges.MustParseBridgeName("testingbridges2"), URL: cltest.WebURL(t, "https://testing.com/tari"), Confirmations: 0, } @@ -215,7 +217,7 @@ func TestBridgeTypesController_Create_Success(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Post( @@ -230,7 +232,7 @@ func TestBridgeTypesController_Create_Success(t *testing.T) { assert.NotEmpty(t, respJSON.Get("data.attributes.incomingToken").String()) assert.NotEmpty(t, respJSON.Get("data.attributes.outgoingToken").String()) - bt, err := app.BridgeORM().FindBridge(bridges.MustNewTaskType(btName)) + bt, err := app.BridgeORM().FindBridge(bridges.MustParseBridgeName(btName)) assert.NoError(t, err) assert.Equal(t, "randomnumber", bt.Name.String()) assert.Equal(t, uint32(10), bt.Confirmations) @@ -243,11 +245,11 @@ func TestBridgeTypesController_Update_Success(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() bt := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("BRidgea"), + Name: bridges.MustParseBridgeName("BRidgea"), URL: cltest.WebURL(t, "http://mybridge"), } require.NoError(t, app.BridgeORM().CreateBridgeType(bt)) @@ -266,12 +268,12 @@ func TestBridgeController_Show(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() bt := &bridges.BridgeType{ - Name: bridges.MustNewTaskType("testingbridges1"), + Name: bridges.MustParseBridgeName("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } @@ -296,7 +298,7 @@ func TestBridgeTypesController_Create_AdapterExistsError(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -312,7 +314,7 @@ func TestBridgeTypesController_Create_BindJSONError(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -328,7 +330,7 @@ func TestBridgeTypesController_Create_DatabaseError(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() diff --git a/core/web/build_info_controller.go b/core/web/build_info_controller.go new file mode 100644 index 00000000000..b98558e1b52 --- /dev/null +++ b/core/web/build_info_controller.go @@ -0,0 +1,20 @@ +package web + +import ( + "net/http" + + "github.com/smartcontractkit/chainlink/core/services/chainlink" + "github.com/smartcontractkit/chainlink/core/static" + + "github.com/gin-gonic/gin" +) + +// BuildVersonController has the build_info endpoint. +type BuildInfoController struct { + App chainlink.Application +} + +// Show returns the build info. +func (eic *BuildInfoController) Show(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"version": static.Version, "commitSHA": static.Sha}) +} diff --git a/core/web/build_info_controller_test.go b/core/web/build_info_controller_test.go new file mode 100644 index 00000000000..5f6fdf9b279 --- /dev/null +++ b/core/web/build_info_controller_test.go @@ -0,0 +1,42 @@ +package web_test + +import ( + "net/http" + "strings" + "testing" + + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + + "github.com/stretchr/testify/require" +) + +func TestBuildInfoController_Show_APICredentials(t *testing.T) { + t.Parallel() + + app := cltest.NewApplicationEVMDisabled(t) + require.NoError(t, app.Start(testutils.Context(t))) + + client := app.NewHTTPClient() + + resp, cleanup := client.Get("/v2/build_info") + defer cleanup() + cltest.AssertServerResponse(t, resp, http.StatusOK) + body := string(cltest.ParseResponseBody(t, resp)) + + require.Contains(t, strings.TrimSpace(body), "commitSHA") + require.Contains(t, strings.TrimSpace(body), "version") +} + +func TestBuildInfoController_Show_NoCredentials(t *testing.T) { + t.Parallel() + + app := cltest.NewApplicationEVMDisabled(t) + require.NoError(t, app.Start(testutils.Context(t))) + + client := http.Client{} + url := app.Config.ClientNodeURL() + "/v2/build_info" + resp, err := client.Get(url) + require.NoError(t, err) + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) +} diff --git a/core/web/config_controller_test.go b/core/web/config_controller_test.go index 7345345f8e7..1817bd69bdd 100644 --- a/core/web/config_controller_test.go +++ b/core/web/config_controller_test.go @@ -11,13 +11,14 @@ import ( evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" "github.com/smartcontractkit/chainlink/core/config" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" ) func TestConfigController_Show(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/config") diff --git a/core/web/eth_keys_controller.go b/core/web/eth_keys_controller.go index 1cdf7eeade1..9300a0389cf 100644 --- a/core/web/eth_keys_controller.go +++ b/core/web/eth_keys_controller.go @@ -307,7 +307,7 @@ func (ekc *ETHKeysController) setEthBalance(ctx context.Context, state ethkey.St bal, err = ethClient.BalanceAt(ctx, state.Address.Address(), nil) } return func(r *presenters.ETHKeyResource) error { - if errors.Cause(err) == evm.ErrNoChains { + if errors.Is(errors.Cause(err), evm.ErrNoChains) { return nil } @@ -334,7 +334,7 @@ func (ekc *ETHKeysController) setLinkBalance(state ethkey.State) presenters.NewE } return func(r *presenters.ETHKeyResource) error { - if errors.Cause(err) == evm.ErrNoChains { + if errors.Is(errors.Cause(err), evm.ErrNoChains) { return nil } if err != nil { @@ -358,7 +358,7 @@ func (ekc *ETHKeysController) setKeyMaxGasPriceWei(state ethkey.State, keyAddres } return func(r *presenters.ETHKeyResource) error { - if errors.Cause(err) == evm.ErrNoChains { + if errors.Is(errors.Cause(err), evm.ErrNoChains) { return nil } if err != nil { diff --git a/core/web/eth_keys_controller_test.go b/core/web/eth_keys_controller_test.go index acd38076343..b97788df8ec 100644 --- a/core/web/eth_keys_controller_test.go +++ b/core/web/eth_keys_controller_test.go @@ -7,6 +7,7 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" webpresenters "github.com/smartcontractkit/chainlink/core/web/presenters" @@ -19,8 +20,7 @@ import ( func TestETHKeysController_Index_Success(t *testing.T) { t.Parallel() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMocksCalled() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.Dev = null.BoolFrom(true) cfg.Overrides.GlobalEvmNonceAutoSync = null.BoolFrom(false) @@ -44,7 +44,7 @@ func TestETHKeysController_Index_Success(t *testing.T) { ethClient.On("GetLINKBalance", mock.Anything, expectedKeys[0].Address.Address()).Return(assets.NewLinkFromJuels(256), nil).Once() ethClient.On("GetLINKBalance", mock.Anything, expectedKeys[1].Address.Address()).Return(assets.NewLinkFromJuels(1), nil).Once() - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/keys/eth") @@ -73,8 +73,7 @@ func TestETHKeysController_Index_Success(t *testing.T) { func TestETHKeysController_Index_NotDev(t *testing.T) { t.Parallel() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMocksCalled() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.Dev = null.BoolFrom(false) cfg.Overrides.GlobalEvmNonceAutoSync = null.BoolFrom(false) @@ -85,7 +84,7 @@ func TestETHKeysController_Index_NotDev(t *testing.T) { ethClient.On("GetLINKBalance", mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(256), nil).Once() app := cltest.NewApplicationWithConfigAndKey(t, cfg, ethClient) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/keys/eth") @@ -110,7 +109,7 @@ func TestETHKeysController_Index_NoAccounts(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -143,7 +142,7 @@ func TestETHKeysController_CreateSuccess(t *testing.T) { client := app.NewHTTPClient() - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) resp, cleanup := client.Post("/v2/keys/eth", nil) defer cleanup() @@ -171,7 +170,7 @@ func TestETHKeysController_UpdateSuccess(t *testing.T) { client := app.NewHTTPClient() - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) resp, cleanup := client.Post("/v2/keys/eth", nil) defer cleanup() diff --git a/core/web/evm_chains_controller.go b/core/web/evm_chains_controller.go index 0ec62eebaeb..823ae57c518 100644 --- a/core/web/evm_chains_controller.go +++ b/core/web/evm_chains_controller.go @@ -68,7 +68,7 @@ func (cc *EVMChainsController) Create(c *gin.Context) { return } - chain, err := cc.App.GetChains().EVM.Add(request.ID.ToInt(), request.Config) + chain, err := cc.App.GetChains().EVM.Add(c, request.ID.ToInt(), request.Config) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) @@ -99,7 +99,7 @@ func (cc *EVMChainsController) Update(c *gin.Context) { return } - chain, err := cc.App.GetChains().EVM.Configure(id.ToInt(), request.Enabled, request.Config) + chain, err := cc.App.GetChains().EVM.Configure(c, id.ToInt(), request.Enabled, request.Config) if errors.Is(err, sql.ErrNoRows) { jsonAPIError(c, http.StatusNotFound, err) diff --git a/core/web/evm_chains_controller_test.go b/core/web/evm_chains_controller_test.go index a9afe3eae6c..5eeb5749bf1 100644 --- a/core/web/evm_chains_controller_test.go +++ b/core/web/evm_chains_controller_test.go @@ -11,18 +11,16 @@ import ( "github.com/manyminds/api2go/jsonapi" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web/presenters" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/web" + "github.com/smartcontractkit/chainlink/core/web/presenters" ) func Test_EVMChainsController_Create(t *testing.T) { @@ -399,7 +397,7 @@ func setupEVMChainsControllerTest(t *testing.T) *TestEVMChainsController { // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app // for the sake of the API endpoints to work properly app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() diff --git a/core/web/evm_forwarders_controller.go b/core/web/evm_forwarders_controller.go new file mode 100644 index 00000000000..654ce664af8 --- /dev/null +++ b/core/web/evm_forwarders_controller.go @@ -0,0 +1,81 @@ +package web + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/core/chains/evm/forwarders" + "github.com/smartcontractkit/chainlink/core/services/chainlink" + "github.com/smartcontractkit/chainlink/core/utils" + "github.com/smartcontractkit/chainlink/core/utils/stringutils" + "github.com/smartcontractkit/chainlink/core/web/presenters" + + "github.com/gin-gonic/gin" +) + +// EVMForwardersController manages EVM chains. +type EVMForwardersController struct { + App chainlink.Application +} + +// Index lists EVM chains. +func (cc *EVMForwardersController) Index(c *gin.Context, size, page, offset int) { + orm := forwarders.NewORM(cc.App.GetSqlxDB(), cc.App.GetLogger(), cc.App.GetConfig()) + fwds, count, err := orm.FindForwarders(0, size) + + if err != nil { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + + var resources []presenters.EVMForwarderResource + for _, fwd := range fwds { + resources = append(resources, presenters.NewEVMForwarderResource(fwd)) + } + + paginatedResponse(c, "forwarder", size, page, resources, count, err) +} + +// CreateEVMChainRequest is a JSONAPI request for creating an EVM chain. +type CreateEVMForwarderRequest struct { + EVMChainID *utils.Big `json:"chainID"` + Address common.Address `json:"address"` +} + +// Create adds a new EVM chain. +func (cc *EVMForwardersController) Create(c *gin.Context) { + request := &CreateEVMForwarderRequest{} + + if err := c.ShouldBindJSON(&request); err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + return + } + orm := forwarders.NewORM(cc.App.GetSqlxDB(), cc.App.GetLogger(), cc.App.GetConfig()) + fwd, err := orm.CreateForwarder(request.Address, *request.EVMChainID) + + if err != nil { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + + jsonAPIResponseWithStatus(c, presenters.NewEVMForwarderResource(fwd), "forwarder", http.StatusCreated) +} + +// Delete removes an EVM chain. +func (cc *EVMForwardersController) Delete(c *gin.Context) { + id, err := stringutils.ToInt32(c.Param("fwdID")) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + return + } + + orm := forwarders.NewORM(cc.App.GetSqlxDB(), cc.App.GetLogger(), cc.App.GetConfig()) + err = orm.DeleteForwarder(id) + + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponseWithStatus(c, nil, "forwarder", http.StatusNoContent) +} diff --git a/core/web/evm_forwarders_controller_test.go b/core/web/evm_forwarders_controller_test.go new file mode 100644 index 00000000000..a26f09ed28c --- /dev/null +++ b/core/web/evm_forwarders_controller_test.go @@ -0,0 +1,125 @@ +package web_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/manyminds/api2go/jsonapi" + "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/smartcontractkit/chainlink/core/utils" + "github.com/smartcontractkit/chainlink/core/web" + "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestEVMForwardersController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupEVMForwardersControllerTest(t *testing.T) *TestEVMForwardersController { + // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app + // for the sake of the API endpoints to work properly + app := cltest.NewApplication(t) + require.NoError(t, app.Start(testutils.Context(t))) + + client := app.NewHTTPClient() + + return &TestEVMForwardersController{ + app: app, + client: client, + } +} + +func Test_EVMForwardersController_Create(t *testing.T) { + t.Parallel() + + controller := setupEVMForwardersControllerTest(t) + + // Setting up chain + chainId := *utils.NewBigI(42) + chaincfg := types.ChainCfg{} + chainSet := controller.app.GetChains().EVM + dbChain, err := chainSet.ORM().CreateChain(utils.Big(*chainId.ToInt()), chaincfg) + require.NoError(t, err) + + // Build EVMForwarderRequest + address := common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81") + body, err := json.Marshal(web.CreateEVMForwarderRequest{ + EVMChainID: &dbChain.ID, + Address: address, + }, + ) + require.NoError(t, err) + + resp, cleanup := controller.client.Post("/v2/nodes/evm/forwarders", bytes.NewReader(body)) + t.Cleanup(cleanup) + require.Equal(t, http.StatusCreated, resp.StatusCode) + + resource := presenters.EVMForwarderResource{} + err = web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource) + require.NoError(t, err) + + assert.Equal(t, resource.Address, address) +} + +func Test_EVMForwardersController_Index(t *testing.T) { + t.Parallel() + + controller := setupEVMForwardersControllerTest(t) + + // Setting up chain + chainId := *utils.NewBigI(42) + chaincfg := types.ChainCfg{} + chainSet := controller.app.GetChains().EVM + dbChain, err := chainSet.ORM().CreateChain(utils.Big(*chainId.ToInt()), chaincfg) + require.NoError(t, err) + + // Build EVMForwarderRequest + fwdrs := []web.CreateEVMForwarderRequest{ + { + EVMChainID: &dbChain.ID, + Address: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), + }, + { + EVMChainID: &dbChain.ID, + Address: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d82"), + }, + } + for _, fwdr := range fwdrs { + + body, err := json.Marshal(web.CreateEVMForwarderRequest{ + EVMChainID: &dbChain.ID, + Address: fwdr.Address, + }, + ) + require.NoError(t, err) + + resp, cleanup := controller.client.Post("/v2/nodes/evm/forwarders", bytes.NewReader(body)) + t.Cleanup(cleanup) + require.Equal(t, http.StatusCreated, resp.StatusCode) + } + + resp, cleanup := controller.client.Get("/v2/nodes/evm/forwarders?size=2") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, len(fwdrs), metaCount) + + var links jsonapi.Links + + var fwdrcs []presenters.EVMForwarderResource + err = web.ParsePaginatedResponse(body, &fwdrcs, &links) + assert.NoError(t, err) + assert.Empty(t, links["prev"].Href) +} diff --git a/core/web/evm_nodes_controller.go b/core/web/evm_nodes_controller.go index 76d068b49af..67e2e22f463 100644 --- a/core/web/evm_nodes_controller.go +++ b/core/web/evm_nodes_controller.go @@ -25,9 +25,10 @@ func (nc *EVMNodesController) Index(c *gin.Context, size, page, offset int) { var count int var err error + chainSet := nc.App.GetChains().EVM if id == "" { // fetch all nodes - nodes, count, err = nc.App.EVMORM().Nodes(offset, size) + nodes, count, err = chainSet.GetNodes(c, offset, size) } else { // fetch nodes for chain ID @@ -36,12 +37,13 @@ func (nc *EVMNodesController) Index(c *gin.Context, size, page, offset int) { jsonAPIError(c, http.StatusBadRequest, err) return } - nodes, count, err = nc.App.EVMORM().NodesForChain(chainID, offset, size) + nodes, count, err = chainSet.GetNodesForChain(c, chainID, offset, size) } var resources []presenters.EVMNodeResource for _, node := range nodes { - resources = append(resources, presenters.NewEVMNodeResource(node)) + res := presenters.NewEVMNodeResource(node) + resources = append(resources, res) } paginatedResponse(c, "node", size, page, resources, count, err) diff --git a/core/web/transactions_controller.go b/core/web/evm_transactions_controller.go similarity index 90% rename from core/web/transactions_controller.go rename to core/web/evm_transactions_controller.go index 7e3ab5e874e..f6896b013d9 100644 --- a/core/web/transactions_controller.go +++ b/core/web/evm_transactions_controller.go @@ -19,7 +19,7 @@ type TransactionsController struct { // Index returns paginated transactions func (tc *TransactionsController) Index(c *gin.Context, size, page, offset int) { - txs, count, err := tc.App.BPTXMORM().EthTransactionsWithAttempts(offset, size) + txs, count, err := tc.App.TxmORM().EthTransactionsWithAttempts(offset, size) ptxs := make([]presenters.EthTxResource, len(txs)) for i, tx := range txs { tx.EthTxAttempts[0].EthTx = tx @@ -34,7 +34,7 @@ func (tc *TransactionsController) Index(c *gin.Context, size, page, offset int) func (tc *TransactionsController) Show(c *gin.Context) { hash := common.HexToHash(c.Param("TxHash")) - ethTxAttempt, err := tc.App.BPTXMORM().FindEthTxAttempt(hash) + ethTxAttempt, err := tc.App.TxmORM().FindEthTxAttempt(hash) if errors.Is(err, sql.ErrNoRows) { jsonAPIError(c, http.StatusNotFound, errors.New("Transaction not found")) return diff --git a/core/web/transactions_controller_test.go b/core/web/evm_transactions_controller_test.go similarity index 89% rename from core/web/transactions_controller_test.go rename to core/web/evm_transactions_controller_test.go index dfe515106ea..82d36315aae 100644 --- a/core/web/transactions_controller_test.go +++ b/core/web/evm_transactions_controller_test.go @@ -6,8 +6,9 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" @@ -21,10 +22,10 @@ func TestTransactionsController_Index_Success(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) db := app.GetSqlxDB() - borm := app.BPTXMORM() + borm := app.TxmORM() ethKeyStore := cltest.NewKeyStore(t, db, app.Config).Eth() client := app.NewHTTPClient() _, from := cltest.MustInsertRandomKey(t, ethKeyStore, 0) @@ -36,7 +37,7 @@ func TestTransactionsController_Index_Success(t *testing.T) { // add second tx attempt for tx2 blockNum := int64(3) attempt := cltest.NewLegacyEthTxAttempt(t, tx2.ID) - attempt.State = bulletprooftxmanager.EthTxAttemptBroadcast + attempt.State = txmgr.EthTxAttemptBroadcast attempt.GasPrice = utils.NewBig(big.NewInt(3)) attempt.BroadcastBeforeBlockNum = &blockNum require.NoError(t, borm.InsertEthTxAttempt(&attempt)) @@ -66,7 +67,7 @@ func TestTransactionsController_Index_Error(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/transactions?size=TrainingDay") @@ -78,9 +79,9 @@ func TestTransactionsController_Show_Success(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) - borm := app.BPTXMORM() + borm := app.TxmORM() client := app.NewHTTPClient() _, from := cltest.MustInsertRandomKey(t, app.KeyStore.Eth(), 0) @@ -111,9 +112,9 @@ func TestTransactionsController_Show_NotFound(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) - borm := app.BPTXMORM() + borm := app.TxmORM() client := app.NewHTTPClient() _, from := cltest.MustInsertRandomKey(t, app.KeyStore.Eth(), 0) tx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, borm, 1, from) diff --git a/core/web/transfer_controller.go b/core/web/evm_transfer_controller.go similarity index 95% rename from core/web/transfer_controller.go rename to core/web/evm_transfer_controller.go index 801d5ea4ede..d9d893e2466 100644 --- a/core/web/transfer_controller.go +++ b/core/web/evm_transfer_controller.go @@ -17,15 +17,15 @@ import ( "github.com/gin-gonic/gin" ) -// TransfersController can send LINK tokens to another address -type TransfersController struct { +// EVMTransfersController can send LINK tokens to another address +type EVMTransfersController struct { App chainlink.Application } // Create sends ETH from the Chainlink's account to a specified address. // // Example: "/withdrawals" -func (tc *TransfersController) Create(c *gin.Context) { +func (tc *EVMTransfersController) Create(c *gin.Context) { var tr models.SendEtherRequest if err := c.ShouldBindJSON(&tr); err != nil { jsonAPIError(c, http.StatusBadRequest, err) diff --git a/core/web/transfer_controller_test.go b/core/web/evm_transfer_controller_test.go similarity index 89% rename from core/web/transfer_controller_test.go rename to core/web/evm_transfer_controller_test.go index c6a5431b30a..a51929f76bc 100644 --- a/core/web/transfer_controller_test.go +++ b/core/web/evm_transfer_controller_test.go @@ -9,13 +9,14 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/store/models" - "gopkg.in/guregu/null.v4" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" ) func TestTransfersController_CreateSuccess_From(t *testing.T) { @@ -23,8 +24,7 @@ func TestTransfersController_CreateSuccess_From(t *testing.T) { key := cltest.MustGenerateRandomKey(t) - ethClient, _, assertMockCalls := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) balance, err := assets.NewEthValueS("200") require.NoError(t, err) @@ -33,7 +33,7 @@ func TestTransfersController_CreateSuccess_From(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(balance.ToInt(), nil) app := cltest.NewApplicationWithKey(t, ethClient, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -64,8 +64,7 @@ func TestTransfersController_CreateSuccess_From_WEI(t *testing.T) { key := cltest.MustGenerateRandomKey(t) - ethClient, _, assertMockCalls := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) balance, err := assets.NewEthValueS("2") require.NoError(t, err) @@ -74,7 +73,7 @@ func TestTransfersController_CreateSuccess_From_WEI(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(balance.ToInt(), nil) app := cltest.NewApplicationWithKey(t, ethClient, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -104,8 +103,7 @@ func TestTransfersController_CreateSuccess_From_BalanceMonitorDisabled(t *testin key := cltest.MustGenerateRandomKey(t) - ethClient, _, assertMockCalls := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) balance, err := assets.NewEthValueS("200") require.NoError(t, err) @@ -117,7 +115,7 @@ func TestTransfersController_CreateSuccess_From_BalanceMonitorDisabled(t *testin config.Overrides.GlobalBalanceMonitorEnabled = null.BoolFrom(false) app := cltest.NewApplicationWithConfigAndKey(t, config, ethClient, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -147,7 +145,7 @@ func TestTransfersController_TransferZeroAddressError(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) amount, err := assets.NewEthValueS("100") require.NoError(t, err) @@ -173,14 +171,13 @@ func TestTransfersController_TransferBalanceToLowError(t *testing.T) { key := cltest.MustGenerateRandomKey(t) - ethClient, _, assertMockCalls := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) ethClient.On("PendingNonceAt", mock.Anything, key.Address.Address()).Return(uint64(1), nil) ethClient.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(assets.NewEth(10).ToInt(), nil) app := cltest.NewApplicationWithKey(t, ethClient, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -208,8 +205,7 @@ func TestTransfersController_TransferBalanceToLowError_ZeroBalance(t *testing.T) key := cltest.MustGenerateRandomKey(t) - ethClient, _, assertMockCalls := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - defer assertMockCalls() + ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) balance, err := assets.NewEthValueS("0") require.NoError(t, err) @@ -218,7 +214,7 @@ func TestTransfersController_TransferBalanceToLowError_ZeroBalance(t *testing.T) ethClient.On("BalanceAt", mock.Anything, key.Address.Address(), (*big.Int)(nil)).Return(balance.ToInt(), nil) app := cltest.NewApplicationWithKey(t, ethClient, key) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -245,7 +241,7 @@ func TestTransfersController_JSONBindingError(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() diff --git a/core/web/tx_attempts_controller.go b/core/web/evm_tx_attempts_controller.go similarity index 90% rename from core/web/tx_attempts_controller.go rename to core/web/evm_tx_attempts_controller.go index bf2193a9bc0..c9fee43d477 100644 --- a/core/web/tx_attempts_controller.go +++ b/core/web/evm_tx_attempts_controller.go @@ -14,7 +14,7 @@ type TxAttemptsController struct { // Index returns paginated transaction attempts func (tac *TxAttemptsController) Index(c *gin.Context, size, page, offset int) { - attempts, count, err := tac.App.BPTXMORM().EthTxAttempts(offset, size) + attempts, count, err := tac.App.TxmORM().EthTxAttempts(offset, size) ptxs := make([]presenters.EthTxResource, len(attempts)) for i, attempt := range attempts { ptxs[i] = presenters.NewEthTxResourceFromAttempt(attempt) diff --git a/core/web/tx_attempts_controller_test.go b/core/web/evm_tx_attempts_controller_test.go similarity index 89% rename from core/web/tx_attempts_controller_test.go rename to core/web/evm_tx_attempts_controller_test.go index b6a202e6ab1..3a3b70cf285 100644 --- a/core/web/tx_attempts_controller_test.go +++ b/core/web/evm_tx_attempts_controller_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" @@ -17,9 +18,9 @@ func TestTxAttemptsController_Index_Success(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) - borm := app.BPTXMORM() + borm := app.TxmORM() client := app.NewHTTPClient() _, from := cltest.MustInsertRandomKey(t, app.KeyStore.Eth(), 0) @@ -48,7 +49,7 @@ func TestTxAttemptsController_Index_Error(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithKey(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/tx_attempts?size=TrainingDay") diff --git a/core/web/external_initiators_controller.go b/core/web/external_initiators_controller.go index 81949aa06ac..7ce67175b90 100644 --- a/core/web/external_initiators_controller.go +++ b/core/web/external_initiators_controller.go @@ -33,7 +33,7 @@ func ValidateExternalInitiator( fe.Add("Name must be alphanumeric and may contain '_' or '-'") } else if _, err := orm.FindExternalInitiatorByName(exi.Name); err == nil { fe.Add(fmt.Sprintf("Name %v already exists", exi.Name)) - } else if err != sql.ErrNoRows { + } else if !errors.Is(err, sql.ErrNoRows) { return errors.Wrap(err, "validating external initiator") } return fe.CoerceEmptyToNil() diff --git a/core/web/external_initiators_controller_test.go b/core/web/external_initiators_controller_test.go index 4004a1d6dd7..3611a0e7afa 100644 --- a/core/web/external_initiators_controller_test.go +++ b/core/web/external_initiators_controller_test.go @@ -7,14 +7,15 @@ import ( "net/http" "testing" - "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/manyminds/api2go/jsonapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,7 +67,7 @@ func TestExternalInitiatorsController_Index(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -129,7 +130,7 @@ func TestExternalInitiatorsController_Create_success(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -154,7 +155,7 @@ func TestExternalInitiatorsController_Create_without_URL(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -179,7 +180,7 @@ func TestExternalInitiatorsController_Create_invalid(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -194,7 +195,7 @@ func TestExternalInitiatorsController_Delete(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) exi := bridges.ExternalInitiator{ Name: "abracadabra", @@ -213,7 +214,7 @@ func TestExternalInitiatorsController_DeleteNotFound(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() diff --git a/core/web/features_controller_test.go b/core/web/features_controller_test.go index 2dc93be5df8..4e5a73a6dce 100644 --- a/core/web/features_controller_test.go +++ b/core/web/features_controller_test.go @@ -6,8 +6,10 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -37,7 +39,7 @@ func setupFeaturesControllerTest(t *testing.T) (*cltest.TestApplication, cltest. os.Setenv("FEATURE_UI_CSA_KEYS", "true") app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() return app, client diff --git a/core/web/feeds_manager_controller_test.go b/core/web/feeds_manager_controller_test.go index 8ee290a050d..b3cdf66296d 100644 --- a/core/web/feeds_manager_controller_test.go +++ b/core/web/feeds_manager_controller_test.go @@ -9,10 +9,12 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/feeds" "github.com/smartcontractkit/chainlink/core/utils/crypto" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" @@ -190,7 +192,7 @@ func setupFeedsManagerTest(t *testing.T) (*cltest.TestApplication, cltest.HTTPCl cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.FeatureFeedsManager = null.BoolFrom(true) app := cltest.NewApplicationWithConfig(t, cfg) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // We need a CSA key to establish a connection to the FMS app.KeyStore.CSA().Create() diff --git a/core/web/gui_assets_test.go b/core/web/gui_assets_test.go index 7136eea02e4..489dd8e4af4 100644 --- a/core/web/gui_assets_test.go +++ b/core/web/gui_assets_test.go @@ -6,16 +6,15 @@ import ( "net/http/httptest" "testing" - "gopkg.in/guregu/null.v4" - - "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/web" + "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" ) //go:embed fixtures/operator_ui/assets @@ -25,7 +24,7 @@ func TestGuiAssets_DefaultIndexHtml_OK(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := &http.Client{} @@ -55,7 +54,7 @@ func TestGuiAssets_DefaultIndexHtml_NotFound(t *testing.T) { t.Parallel() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := &http.Client{} @@ -88,7 +87,7 @@ func TestGuiAssets_DefaultIndexHtml_RateLimited(t *testing.T) { config := cltest.NewTestGeneralConfig(t) config.Overrides.Dev = null.BoolFrom(false) app := cltest.NewApplicationWithConfig(t, config) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := &http.Client{} diff --git a/core/web/health_controller_test.go b/core/web/health_controller_test.go index ef18eca0141..f8739b4ac54 100644 --- a/core/web/health_controller_test.go +++ b/core/web/health_controller_test.go @@ -6,6 +6,8 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" + "github.com/smartcontractkit/chainlink/core/internal/testutils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -36,7 +38,7 @@ func TestHealthController_Readyz(t *testing.T) { healthChecker.On("Close").Return(nil).Once() app.HealthChecker = healthChecker - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() resp, cleanup := client.Get("/readyz") diff --git a/core/web/jobs_controller.go b/core/web/jobs_controller.go index 72c566d6370..aa29ff2f84b 100644 --- a/core/web/jobs_controller.go +++ b/core/web/jobs_controller.go @@ -6,14 +6,11 @@ import ( "net/http" "time" - "github.com/smartcontractkit/chainlink/core/services/blockhashstore" - "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" - "github.com/gin-gonic/gin" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" + "github.com/smartcontractkit/chainlink/core/services/blockhashstore" "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/services/cron" "github.com/smartcontractkit/chainlink/core/services/directrequest" @@ -21,7 +18,9 @@ import ( "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keeper" "github.com/smartcontractkit/chainlink/core/services/keystore" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/services/ocr" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/services/vrf" "github.com/smartcontractkit/chainlink/core/services/webhook" @@ -73,7 +72,7 @@ func (jc *JobsController) Show(c *gin.Context) { return } if err != nil { - if errors.Cause(err) == sql.ErrNoRows { + if errors.Is(errors.Cause(err), sql.ErrNoRows) { jsonAPIError(c, http.StatusNotFound, errors.New("job not found")) } else { jsonAPIError(c, http.StatusInternalServerError, err) @@ -109,13 +108,13 @@ func (jc *JobsController) Create(c *gin.Context) { config := jc.App.GetConfig() switch jobType { case job.OffchainReporting: - jb, err = offchainreporting.ValidatedOracleSpecToml(jc.App.GetChains().EVM, request.TOML) + jb, err = ocr.ValidatedOracleSpecToml(jc.App.GetChains().EVM, request.TOML) if !config.Dev() && !config.FeatureOffchainReporting() { jsonAPIError(c, http.StatusNotImplemented, errors.New("The Offchain Reporting feature is disabled by configuration")) return } case job.OffchainReporting2: - jb, err = offchainreporting2.ValidatedOracleSpecToml(jc.App.GetConfig(), request.TOML) + jb, err = validate.ValidatedOracleSpecToml(jc.App.GetConfig(), request.TOML) if !config.Dev() && !config.FeatureOffchainReporting2() { jsonAPIError(c, http.StatusNotImplemented, errors.New("The Offchain Reporting 2 feature is disabled by configuration")) return @@ -149,7 +148,7 @@ func (jc *JobsController) Create(c *gin.Context) { defer cancel() err = jc.App.AddJobV2(ctx, &jb) if err != nil { - if errors.Cause(err) == job.ErrNoSuchKeyBundle || errors.As(err, &keystore.KeyNotFoundError{}) || errors.Cause(err) == job.ErrNoSuchTransmitterKey { + if errors.Is(errors.Cause(err), job.ErrNoSuchKeyBundle) || errors.As(err, &keystore.KeyNotFoundError{}) || errors.Is(errors.Cause(err), job.ErrNoSuchTransmitterKey) { jsonAPIError(c, http.StatusBadRequest, err) return } diff --git a/core/web/jobs_controller_test.go b/core/web/jobs_controller_test.go index 4e52f80144d..e8de1586f4b 100644 --- a/core/web/jobs_controller_test.go +++ b/core/web/jobs_controller_test.go @@ -14,7 +14,12 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/services/pg" + p2ppeer "github.com/libp2p/go-libp2p-core/peer" + "github.com/pelletier/go-toml" + "github.com/smartcontractkit/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/testutils" @@ -22,16 +27,10 @@ import ( "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/testdata/testspecs" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" - - p2ppeer "github.com/libp2p/go-libp2p-core/peer" - "github.com/pelletier/go-toml" - "github.com/smartcontractkit/sqlx" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" ) func TestJobsController_Create_ValidationFailure_OffchainReportingSpec(t *testing.T) { @@ -164,23 +163,24 @@ func TestJobController_Create_HappyPath(t *testing.T) { require.NotNil(t, resource.OffChainReportingSpec) assert.Equal(t, "web oracle spec", jb.Name.ValueOrZero()) - assert.Equal(t, jb.OffchainreportingOracleSpec.P2PBootstrapPeers, resource.OffChainReportingSpec.P2PBootstrapPeers) - assert.Equal(t, jb.OffchainreportingOracleSpec.IsBootstrapPeer, resource.OffChainReportingSpec.IsBootstrapPeer) - assert.Equal(t, jb.OffchainreportingOracleSpec.EncryptedOCRKeyBundleID, resource.OffChainReportingSpec.EncryptedOCRKeyBundleID) - assert.Equal(t, jb.OffchainreportingOracleSpec.TransmitterAddress, resource.OffChainReportingSpec.TransmitterAddress) - assert.Equal(t, jb.OffchainreportingOracleSpec.ObservationTimeout, resource.OffChainReportingSpec.ObservationTimeout) - assert.Equal(t, jb.OffchainreportingOracleSpec.BlockchainTimeout, resource.OffChainReportingSpec.BlockchainTimeout) - assert.Equal(t, jb.OffchainreportingOracleSpec.ContractConfigTrackerSubscribeInterval, resource.OffChainReportingSpec.ContractConfigTrackerSubscribeInterval) - assert.Equal(t, jb.OffchainreportingOracleSpec.ContractConfigTrackerSubscribeInterval, resource.OffChainReportingSpec.ContractConfigTrackerSubscribeInterval) - assert.Equal(t, jb.OffchainreportingOracleSpec.ContractConfigConfirmations, resource.OffChainReportingSpec.ContractConfigConfirmations) + assert.Equal(t, jb.OCROracleSpec.P2PBootstrapPeers, resource.OffChainReportingSpec.P2PBootstrapPeers) + assert.Equal(t, jb.OCROracleSpec.IsBootstrapPeer, resource.OffChainReportingSpec.IsBootstrapPeer) + assert.Equal(t, jb.OCROracleSpec.EncryptedOCRKeyBundleID, resource.OffChainReportingSpec.EncryptedOCRKeyBundleID) + assert.Equal(t, jb.OCROracleSpec.TransmitterAddress, resource.OffChainReportingSpec.TransmitterAddress) + assert.Equal(t, jb.OCROracleSpec.ObservationTimeout, resource.OffChainReportingSpec.ObservationTimeout) + assert.Equal(t, jb.OCROracleSpec.BlockchainTimeout, resource.OffChainReportingSpec.BlockchainTimeout) + assert.Equal(t, jb.OCROracleSpec.ContractConfigTrackerSubscribeInterval, resource.OffChainReportingSpec.ContractConfigTrackerSubscribeInterval) + assert.Equal(t, jb.OCROracleSpec.ContractConfigTrackerSubscribeInterval, resource.OffChainReportingSpec.ContractConfigTrackerSubscribeInterval) + assert.Equal(t, jb.OCROracleSpec.ContractConfigConfirmations, resource.OffChainReportingSpec.ContractConfigConfirmations) assert.NotNil(t, resource.PipelineSpec.DotDAGSource) // Sanity check to make sure it inserted correctly - require.Equal(t, ethkey.EIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C"), jb.OffchainreportingOracleSpec.ContractAddress) + require.Equal(t, ethkey.EIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C"), jb.OCROracleSpec.ContractAddress) }, }, { name: "keeper", toml: testspecs.GenerateKeeperSpec(testspecs.KeeperSpecParams{ + Name: "example keeper spec", ContractAddress: "0x9E40733cC9df84636505f4e6Db28DCa0dC5D1bba", FromAddress: "0xa8037A20989AFcBC51798de9762b351D63ff462e", }).Toml(), @@ -326,7 +326,7 @@ func TestJobController_Create_HappyPath(t *testing.T) { func TestJobsController_Create_WebhookSpec(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) _, fetchBridge := cltest.MustCreateBridge(t, app.GetSqlxDB(), cltest.BridgeOpts{}, app.GetConfig()) _, submitBridge := cltest.MustCreateBridge(t, app.GetSqlxDB(), cltest.BridgeOpts{}, app.GetConfig()) @@ -352,7 +352,7 @@ func TestJobsController_Create_WebhookSpec(t *testing.T) { func TestJobsController_FailToCreate_EmptyJsonAttribute(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -447,7 +447,7 @@ func TestJobsController_Show_NonExistentID(t *testing.T) { } func runOCRJobSpecAssertions(t *testing.T, ocrJobSpecFromFileDB job.Job, ocrJobSpecFromServer presenters.JobResource) { - ocrJobSpecFromFile := ocrJobSpecFromFileDB.OffchainreportingOracleSpec + ocrJobSpecFromFile := ocrJobSpecFromFileDB.OCROracleSpec assert.Equal(t, ocrJobSpecFromFile.ContractAddress, ocrJobSpecFromServer.OffChainReportingSpec.ContractAddress) assert.Equal(t, ocrJobSpecFromFile.P2PBootstrapPeers, ocrJobSpecFromServer.OffChainReportingSpec.P2PBootstrapPeers) assert.Equal(t, ocrJobSpecFromFile.IsBootstrapPeer, ocrJobSpecFromServer.OffChainReportingSpec.IsBootstrapPeer) @@ -487,7 +487,7 @@ func setupJobsControllerTests(t *testing.T) (ta *cltest.TestApplication, cc clte cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.FeatureOffchainReporting = null.BoolFrom(true) app := cltest.NewApplicationWithConfigAndKey(t, cfg) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() vrfKeyStore := app.GetKeyStore().VRF() @@ -502,7 +502,7 @@ func setupJobSpecsControllerTestsWithJobs(t *testing.T) (*cltest.TestApplication app := cltest.NewApplicationWithConfigAndKey(t, cfg) require.NoError(t, app.KeyStore.OCR().Add(cltest.DefaultOCRKey)) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) _, bridge := cltest.MustCreateBridge(t, app.GetSqlxDB(), cltest.BridgeOpts{}, app.GetConfig()) _, bridge2 := cltest.MustCreateBridge(t, app.GetSqlxDB(), cltest.BridgeOpts{}, app.GetConfig()) @@ -513,11 +513,11 @@ func setupJobSpecsControllerTestsWithJobs(t *testing.T) (*cltest.TestApplication ocrspec := testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{DS1BridgeName: bridge.Name.String(), DS2BridgeName: bridge2.Name.String()}) err := toml.Unmarshal([]byte(ocrspec.Toml()), &jb) require.NoError(t, err) - var ocrSpec job.OffchainReportingOracleSpec + var ocrSpec job.OCROracleSpec err = toml.Unmarshal([]byte(ocrspec.Toml()), &ocrspec) require.NoError(t, err) - jb.OffchainreportingOracleSpec = &ocrSpec - jb.OffchainreportingOracleSpec.TransmitterAddress = &app.Key.Address + jb.OCROracleSpec = &ocrSpec + jb.OCROracleSpec.TransmitterAddress = &app.Key.Address err = app.AddJobV2(context.Background(), &jb) require.NoError(t, err) diff --git a/core/web/loader/eth_transaction_attempt.go b/core/web/loader/eth_transaction_attempt.go index 10243062b1e..a96d8b9e2d2 100644 --- a/core/web/loader/eth_transaction_attempt.go +++ b/core/web/loader/eth_transaction_attempt.go @@ -5,7 +5,7 @@ import ( "github.com/graph-gophers/dataloader" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/utils/stringutils" ) @@ -28,13 +28,13 @@ func (b *ethTransactionAttemptBatcher) loadByEthTransactionIDs(ctx context.Conte keyOrder[key.String()] = ix } - attempts, err := b.app.BPTXMORM().FindEthTxAttemptsByEthTxIDs(ethTxsIDs) + attempts, err := b.app.TxmORM().FindEthTxAttemptsByEthTxIDs(ethTxsIDs) if err != nil { return []*dataloader.Result{{Data: nil, Error: err}} } // Generate a map of attempts to txIDs - attemptsForTx := map[string][]bulletprooftxmanager.EthTxAttempt{} + attemptsForTx := map[string][]txmgr.EthTxAttempt{} for _, a := range attempts { id := stringutils.FromInt64(a.EthTxID) @@ -54,7 +54,7 @@ func (b *ethTransactionAttemptBatcher) loadByEthTransactionIDs(ctx context.Conte // fill array positions without any attempts as an empty slice for _, ix := range keyOrder { - results[ix] = &dataloader.Result{Data: []bulletprooftxmanager.EthTxAttempt{}, Error: nil} + results[ix] = &dataloader.Result{Data: []txmgr.EthTxAttempt{}, Error: nil} } return results diff --git a/core/web/loader/getters.go b/core/web/loader/getters.go index 32ab75b685c..1eb4261231d 100644 --- a/core/web/loader/getters.go +++ b/core/web/loader/getters.go @@ -2,13 +2,12 @@ package loader import ( "context" - "fmt" "github.com/graph-gophers/dataloader" "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/services/feeds" "github.com/smartcontractkit/chainlink/core/services/job" @@ -16,6 +15,9 @@ import ( "github.com/smartcontractkit/chainlink/core/utils/stringutils" ) +// ErrInvalidType indicates that results loaded is not the type expected +var ErrInvalidType = errors.New("invalid type") + // GetChainByID fetches the chain by it's id. func GetChainByID(ctx context.Context, id string) (*types.Chain, error) { ldr := For(ctx) @@ -28,7 +30,7 @@ func GetChainByID(ctx context.Context, id string) (*types.Chain, error) { chain, ok := result.(types.Chain) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return &chain, nil @@ -46,7 +48,7 @@ func GetNodesByChainID(ctx context.Context, id string) ([]types.Node, error) { nodes, ok := result.([]types.Node) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return nodes, nil @@ -64,7 +66,7 @@ func GetFeedsManagerByID(ctx context.Context, id string) (*feeds.FeedsManager, e mgr, ok := result.(feeds.FeedsManager) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return &mgr, nil @@ -109,7 +111,7 @@ func GetSpecsByJobProposalID(ctx context.Context, jpID string) ([]feeds.JobPropo specs, ok := result.([]feeds.JobProposalSpec) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return specs, nil @@ -127,7 +129,7 @@ func GetLatestSpecByJobProposalID(ctx context.Context, jpID string) (*feeds.JobP specs, ok := result.([]feeds.JobProposalSpec) if !ok { - return nil, fmt.Errorf("invalid type: %T", result) + return nil, errors.Wrapf(ErrInvalidType, "Result : %T", result) } max := specs[0] @@ -152,12 +154,30 @@ func GetJobProposalsByFeedsManagerID(ctx context.Context, id string) ([]feeds.Jo jbRuns, ok := result.([]feeds.JobProposal) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return jbRuns, nil } +// GetJobByExternalJobID fetches the job proposals by external job ID +func GetJobByExternalJobID(ctx context.Context, id string) (*job.Job, error) { + ldr := For(ctx) + + thunk := ldr.JobsByExternalJobIDs.Load(ctx, dataloader.StringKey(id)) + result, err := thunk() + if err != nil { + return nil, err + } + + job, ok := result.(job.Job) + if !ok { + return nil, ErrInvalidType + } + + return &job, nil +} + // GetJobByPipelineSpecID fetches the job by pipeline spec ID. func GetJobByPipelineSpecID(ctx context.Context, id string) (*job.Job, error) { ldr := For(ctx) @@ -170,14 +190,14 @@ func GetJobByPipelineSpecID(ctx context.Context, id string) (*job.Job, error) { jb, ok := result.(job.Job) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return &jb, nil } // GetEthTxAttemptsByEthTxID fetches the attempts for an eth transaction. -func GetEthTxAttemptsByEthTxID(ctx context.Context, id string) ([]bulletprooftxmanager.EthTxAttempt, error) { +func GetEthTxAttemptsByEthTxID(ctx context.Context, id string) ([]txmgr.EthTxAttempt, error) { ldr := For(ctx) thunk := ldr.EthTxAttemptsByEthTxIDLoader.Load(ctx, dataloader.StringKey(id)) @@ -186,9 +206,9 @@ func GetEthTxAttemptsByEthTxID(ctx context.Context, id string) ([]bulletprooftxm return nil, err } - attempts, ok := result.([]bulletprooftxmanager.EthTxAttempt) + attempts, ok := result.([]txmgr.EthTxAttempt) if !ok { - return nil, errors.New("invalid type") + return nil, ErrInvalidType } return attempts, nil diff --git a/core/web/loader/job.go b/core/web/loader/job.go index 2f483bd4c80..d1136de2072 100644 --- a/core/web/loader/job.go +++ b/core/web/loader/job.go @@ -5,8 +5,10 @@ import ( "errors" "github.com/graph-gophers/dataloader" + uuid "github.com/satori/go.uuid" "github.com/smartcontractkit/chainlink/core/services/chainlink" + "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/utils/stringutils" ) @@ -14,6 +16,53 @@ type jobBatcher struct { app chainlink.Application } +func (b *jobBatcher) loadByExternalJobIDs(_ context.Context, keys dataloader.Keys) []*dataloader.Result { + // Create a map for remembering the order of keys passed in + keyOrder := make(map[string]int, len(keys)) + // Collect the keys to search for + var jobIDs []uuid.UUID + for ix, key := range keys { + id, err := uuid.FromString(key.String()) + if err == nil { + jobIDs = append(jobIDs, id) + } + + keyOrder[key.String()] = ix + } + + // Fetch the jobs + var jobs []job.Job + for _, id := range jobIDs { + job, err := b.app.JobORM().FindJobByExternalJobID(id) + + if err != nil { + return []*dataloader.Result{{Data: nil, Error: err}} + } + + jobs = append(jobs, job) + } + + // Construct the output array of dataloader results + results := make([]*dataloader.Result, len(keys)) + for _, j := range jobs { + id := j.ExternalJobID.String() + + ix, ok := keyOrder[id] + // if found, remove from index lookup map, so we know elements were found + if ok { + results[ix] = &dataloader.Result{Data: j, Error: nil} + delete(keyOrder, id) + } + } + + // fill array positions without any feeds managers + for _, ix := range keyOrder { + results[ix] = &dataloader.Result{Data: nil, Error: errors.New("feeds manager not found")} + } + + return results +} + func (b *jobBatcher) loadByPipelineSpecIDs(_ context.Context, keys dataloader.Keys) []*dataloader.Result { // Create a map for remembering the order of keys passed in keyOrder := make(map[string]int, len(keys)) diff --git a/core/web/loader/loader.go b/core/web/loader/loader.go index f983e10a85d..174f62df740 100644 --- a/core/web/loader/loader.go +++ b/core/web/loader/loader.go @@ -14,14 +14,15 @@ type loadersKey struct{} type Dataloader struct { app chainlink.Application - NodesByChainIDLoader *dataloader.Loader ChainsByIDLoader *dataloader.Loader + EthTxAttemptsByEthTxIDLoader *dataloader.Loader FeedsManagersByIDLoader *dataloader.Loader - JobRunsByIDLoader *dataloader.Loader - JobsByPipelineSpecIDLoader *dataloader.Loader JobProposalsByManagerIDLoader *dataloader.Loader JobProposalSpecsByJobProposalID *dataloader.Loader - EthTxAttemptsByEthTxIDLoader *dataloader.Loader + JobRunsByIDLoader *dataloader.Loader + JobsByExternalJobIDs *dataloader.Loader + JobsByPipelineSpecIDLoader *dataloader.Loader + NodesByChainIDLoader *dataloader.Loader } func New(app chainlink.Application) *Dataloader { @@ -37,14 +38,15 @@ func New(app chainlink.Application) *Dataloader { return &Dataloader{ app: app, - NodesByChainIDLoader: dataloader.NewBatchedLoader(nodes.loadByChainIDs), ChainsByIDLoader: dataloader.NewBatchedLoader(chains.loadByIDs), + EthTxAttemptsByEthTxIDLoader: dataloader.NewBatchedLoader(attmpts.loadByEthTransactionIDs), FeedsManagersByIDLoader: dataloader.NewBatchedLoader(mgrs.loadByIDs), - JobRunsByIDLoader: dataloader.NewBatchedLoader(jobRuns.loadByIDs), - JobsByPipelineSpecIDLoader: dataloader.NewBatchedLoader(jbs.loadByPipelineSpecIDs), JobProposalsByManagerIDLoader: dataloader.NewBatchedLoader(jps.loadByManagersIDs), JobProposalSpecsByJobProposalID: dataloader.NewBatchedLoader(jpSpecs.loadByJobProposalsIDs), - EthTxAttemptsByEthTxIDLoader: dataloader.NewBatchedLoader(attmpts.loadByEthTransactionIDs), + JobRunsByIDLoader: dataloader.NewBatchedLoader(jobRuns.loadByIDs), + JobsByExternalJobIDs: dataloader.NewBatchedLoader(jbs.loadByExternalJobIDs), + JobsByPipelineSpecIDLoader: dataloader.NewBatchedLoader(jbs.loadByPipelineSpecIDs), + NodesByChainIDLoader: dataloader.NewBatchedLoader(nodes.loadByChainIDs), } } diff --git a/core/web/loader/loader_test.go b/core/web/loader/loader_test.go index ee92738bd49..d0de11e86c1 100644 --- a/core/web/loader/loader_test.go +++ b/core/web/loader/loader_test.go @@ -6,15 +6,17 @@ import ( "testing" "github.com/graph-gophers/dataloader" + uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - bulletprooftxmanagerMocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" - evmORMMocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + evmmocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + txmgrMocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/core/chains/evm/types" coremocks "github.com/smartcontractkit/chainlink/core/internal/mocks" + "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/services/feeds" feedsMocks "github.com/smartcontractkit/chainlink/core/services/feeds/mocks" "github.com/smartcontractkit/chainlink/core/services/job" @@ -26,12 +28,12 @@ import ( func TestLoader_Chains(t *testing.T) { t.Parallel() - emvORM := &evmORMMocks.ORM{} + evmORM := &evmmocks.ORM{} app := &coremocks.Application{} ctx := InjectDataloader(context.Background(), app) defer t.Cleanup(func() { - mock.AssertExpectationsForObjects(t, app, emvORM) + mock.AssertExpectationsForObjects(t, app, evmORM) }) id := utils.Big{} @@ -55,11 +57,11 @@ func TestLoader_Chains(t *testing.T) { Enabled: true, } - emvORM.On("GetChainsByIDs", []utils.Big{id2, id, chainId3}).Return([]types.Chain{ + evmORM.On("GetChainsByIDs", []utils.Big{id2, id, chainId3}).Return([]types.Chain{ chain, chain2, }, nil) - app.On("EVMORM").Return(emvORM) + app.On("EVMORM").Return(evmORM) batcher := chainBatcher{app} @@ -77,12 +79,14 @@ func TestLoader_Chains(t *testing.T) { func TestLoader_Nodes(t *testing.T) { t.Parallel() - emvORM := &evmORMMocks.ORM{} + evmChainSet := new(evmmocks.ChainSet) + evmChainSet.Test(t) app := &coremocks.Application{} + app.Test(t) ctx := InjectDataloader(context.Background(), app) defer t.Cleanup(func() { - mock.AssertExpectationsForObjects(t, app, emvORM) + mock.AssertExpectationsForObjects(t, app, evmChainSet) }) chainId1 := utils.Big{} @@ -108,10 +112,10 @@ func TestLoader_Nodes(t *testing.T) { EVMChainID: chainId2, } - emvORM.On("GetNodesByChainIDs", []utils.Big{chainId2, chainId1, chainId3}).Return([]types.Node{ + evmChainSet.On("GetNodesByChainIDs", mock.Anything, []utils.Big{chainId2, chainId1, chainId3}).Return([]types.Node{ node1, node2, }, nil) - app.On("EVMORM").Return(emvORM) + app.On("GetChains").Return(chainlink.Chains{EVM: evmChainSet}) batcher := nodeBatcher{app} @@ -300,32 +304,62 @@ func TestLoader_JobsByPipelineSpecIDs(t *testing.T) { }) } +func TestLoader_JobsByExternalJobIDs(t *testing.T) { + t.Parallel() + + t.Run("with out errors", func(t *testing.T) { + t.Parallel() + + jobsORM := &jobORMMocks.ORM{} + app := &coremocks.Application{} + ctx := InjectDataloader(context.Background(), app) + + defer t.Cleanup(func() { + mock.AssertExpectationsForObjects(t, app, jobsORM) + }) + + ejID := uuid.NewV4() + job := job.Job{ID: int32(2), ExternalJobID: ejID} + + jobsORM.On("FindJobByExternalJobID", ejID).Return(job, nil) + app.On("JobORM").Return(jobsORM) + + batcher := jobBatcher{app} + + keys := dataloader.NewKeysFromStrings([]string{ejID.String()}) + found := batcher.loadByExternalJobIDs(ctx, keys) + + require.Len(t, found, 1) + assert.Equal(t, job, found[0].Data) + }) +} + func TestLoader_EthTransactionsAttempts(t *testing.T) { t.Parallel() - bptxmORM := &bulletprooftxmanagerMocks.ORM{} + txmORM := &txmgrMocks.ORM{} app := &coremocks.Application{} ctx := InjectDataloader(context.Background(), app) defer t.Cleanup(func() { - mock.AssertExpectationsForObjects(t, app, bptxmORM) + mock.AssertExpectationsForObjects(t, app, txmORM) }) ethTxIDs := []int64{1, 2, 3} - attempt1 := bulletprooftxmanager.EthTxAttempt{ + attempt1 := txmgr.EthTxAttempt{ ID: int64(1), EthTxID: ethTxIDs[0], } - attempt2 := bulletprooftxmanager.EthTxAttempt{ + attempt2 := txmgr.EthTxAttempt{ ID: int64(1), EthTxID: ethTxIDs[1], } - bptxmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{ethTxIDs[2], ethTxIDs[1], ethTxIDs[0]}).Return([]bulletprooftxmanager.EthTxAttempt{ + txmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{ethTxIDs[2], ethTxIDs[1], ethTxIDs[0]}).Return([]txmgr.EthTxAttempt{ attempt1, attempt2, }, nil) - app.On("BPTXMORM").Return(bptxmORM) + app.On("TxmORM").Return(txmORM) batcher := ethTransactionAttemptBatcher{app} @@ -333,7 +367,7 @@ func TestLoader_EthTransactionsAttempts(t *testing.T) { found := batcher.loadByEthTransactionIDs(ctx, keys) require.Len(t, found, 3) - assert.Equal(t, []bulletprooftxmanager.EthTxAttempt{}, found[0].Data) - assert.Equal(t, []bulletprooftxmanager.EthTxAttempt{attempt2}, found[1].Data) - assert.Equal(t, []bulletprooftxmanager.EthTxAttempt{attempt1}, found[2].Data) + assert.Equal(t, []txmgr.EthTxAttempt{}, found[0].Data) + assert.Equal(t, []txmgr.EthTxAttempt{attempt2}, found[1].Data) + assert.Equal(t, []txmgr.EthTxAttempt{attempt1}, found[2].Data) } diff --git a/core/web/loader/node.go b/core/web/loader/node.go index 4ce536e5fa7..8759c84a37e 100644 --- a/core/web/loader/node.go +++ b/core/web/loader/node.go @@ -28,7 +28,7 @@ func (b *nodeBatcher) loadByChainIDs(ctx context.Context, keys dataloader.Keys) keyOrder[key.String()] = ix } - nodes, err := b.app.EVMORM().GetNodesByChainIDs(chainIDs) + nodes, err := b.app.GetChains().EVM.GetNodesByChainIDs(ctx, chainIDs) if err != nil { return []*dataloader.Result{{Data: nil, Error: err}} } diff --git a/core/web/log_controller_test.go b/core/web/log_controller_test.go index 212cb0e5865..be8367f2b90 100644 --- a/core/web/log_controller_test.go +++ b/core/web/log_controller_test.go @@ -14,6 +14,7 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" @@ -45,7 +46,7 @@ func TestLogController_GetLogConfig(t *testing.T) { cfg.Overrides.DefaultLogLevel = &defaultLogLevel app := cltest.NewApplicationWithConfig(t, cfg) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -179,7 +180,7 @@ func TestLogController_PatchLogConfig(t *testing.T) { tc := tc t.Run(tc.Description, func(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() request := web.LogPatchRequest{Level: tc.logLevel, SqlEnabled: tc.logSql} diff --git a/core/web/ocr2_keys_controller.go b/core/web/ocr2_keys_controller.go index 6de0b7a526c..e95e8347f9d 100644 --- a/core/web/ocr2_keys_controller.go +++ b/core/web/ocr2_keys_controller.go @@ -34,7 +34,7 @@ func (ocr2kc *OCR2KeysController) Index(c *gin.Context) { func (ocr2kc *OCR2KeysController) Create(c *gin.Context) { chainType := chaintype.ChainType(c.Param("chainType")) key, err := ocr2kc.App.GetKeyStore().OCR2().Create(chainType) - if errors.Cause(err) == chaintype.ErrInvalidChainType { + if errors.Is(errors.Cause(err), chaintype.ErrInvalidChainType) { jsonAPIError(c, http.StatusBadRequest, err) return } diff --git a/core/web/ocr2_keys_controller_test.go b/core/web/ocr2_keys_controller_test.go index faedc55290a..99893b252a4 100644 --- a/core/web/ocr2_keys_controller_test.go +++ b/core/web/ocr2_keys_controller_test.go @@ -6,11 +6,13 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -98,7 +100,7 @@ func setupOCR2KeysControllerTests(t *testing.T) (cltest.HTTPClientCleaner, keyst t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() app.KeyStore.OCR2().Add(cltest.DefaultOCR2Key) diff --git a/core/web/ocr_keys_controller_test.go b/core/web/ocr_keys_controller_test.go index 53312c678eb..91684983e7a 100644 --- a/core/web/ocr_keys_controller_test.go +++ b/core/web/ocr_keys_controller_test.go @@ -5,10 +5,12 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -86,7 +88,7 @@ func setupOCRKeysControllerTests(t *testing.T) (cltest.HTTPClientCleaner, keysto t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() app.KeyStore.OCR().Add(cltest.DefaultOCRKey) diff --git a/core/web/p2p_keys_controller_test.go b/core/web/p2p_keys_controller_test.go index 65148e9ea07..9025f1ac072 100644 --- a/core/web/p2p_keys_controller_test.go +++ b/core/web/p2p_keys_controller_test.go @@ -6,11 +6,13 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -40,7 +42,7 @@ func TestP2PKeysController_Create_HappyPath(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() keyStore := app.GetKeyStore() @@ -109,7 +111,7 @@ func setupP2PKeysControllerTests(t *testing.T) (cltest.HTTPClientCleaner, keysto t.Helper() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) app.KeyStore.OCR().Add(cltest.DefaultOCRKey) app.KeyStore.P2P().Add(cltest.DefaultP2PKey) diff --git a/core/web/ping_controller_test.go b/core/web/ping_controller_test.go index c9c061aeae6..23758212de7 100644 --- a/core/web/ping_controller_test.go +++ b/core/web/ping_controller_test.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/require" @@ -17,7 +18,7 @@ func TestPingController_Show_APICredentials(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -32,7 +33,7 @@ func TestPingController_Show_ExternalInitiatorCredentials(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) eia := &auth.Token{ AccessKey: "abracadabra", @@ -70,7 +71,7 @@ func TestPingController_Show_NoCredentials(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := http.Client{} url := app.Config.ClientNodeURL() + "/v2/ping" diff --git a/core/web/pipeline_runs_controller_test.go b/core/web/pipeline_runs_controller_test.go index ee8740b6156..c7ed1663b8a 100644 --- a/core/web/pipeline_runs_controller_test.go +++ b/core/web/pipeline_runs_controller_test.go @@ -29,8 +29,7 @@ import ( func TestPipelineRunsController_CreateWithBody_HappyPath(t *testing.T) { t.Parallel() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMocksCalled() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.SetDefaultHTTPTimeout(2 * time.Second) @@ -38,7 +37,7 @@ func TestPipelineRunsController_CreateWithBody_HappyPath(t *testing.T) { cfg.Overrides.EVMRPCEnabled = null.BoolFrom(false) app := cltest.NewApplicationWithConfig(t, cfg, ethClient) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Setup the bridge mockServer := cltest.NewHTTPMockServerWithRequest(t, 200, `{}`, func(r *http.Request) { @@ -88,8 +87,7 @@ func TestPipelineRunsController_CreateWithBody_HappyPath(t *testing.T) { func TestPipelineRunsController_CreateNoBody_HappyPath(t *testing.T) { t.Parallel() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMocksCalled() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.SetDefaultHTTPTimeout(2 * time.Second) @@ -97,7 +95,7 @@ func TestPipelineRunsController_CreateNoBody_HappyPath(t *testing.T) { cfg.Overrides.EVMRPCEnabled = null.BoolFrom(false) app := cltest.NewApplicationWithConfig(t, cfg, ethClient) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) // Setup the bridges mockServer := cltest.NewHTTPMockServer(t, 200, "POST", `{"data":{"result":"123.45"}}`) @@ -237,7 +235,7 @@ func TestPipelineRunsController_Show_HappyPath(t *testing.T) { func TestPipelineRunsController_ShowRun_InvalidID(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() response, cleanup := client.Get("/v2/jobs/1/runs/invalid-run-ID") @@ -247,13 +245,12 @@ func TestPipelineRunsController_ShowRun_InvalidID(t *testing.T) { func setupPipelineRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, int32, []int64) { t.Parallel() - ethClient, _, assertMocksCalled := cltest.NewEthMocksWithStartupAssertions(t) - defer assertMocksCalled() + ethClient := cltest.NewEthMocksWithStartupAssertions(t) cfg := cltest.NewTestGeneralConfig(t) cfg.Overrides.EVMRPCEnabled = null.BoolFrom(false) cfg.Overrides.FeatureOffchainReporting = null.BoolFrom(true) app := cltest.NewApplicationWithConfig(t, cfg, ethClient) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) app.KeyStore.OCR().Add(cltest.DefaultOCRKey) app.KeyStore.P2P().Add(cltest.DefaultP2PKey) client := app.NewHTTPClient() @@ -292,10 +289,10 @@ func setupPipelineRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, i var jb job.Job err := toml.Unmarshal([]byte(sp), &jb) require.NoError(t, err) - var os job.OffchainReportingOracleSpec + var os job.OCROracleSpec err = toml.Unmarshal([]byte(sp), &os) require.NoError(t, err) - jb.OffchainreportingOracleSpec = &os + jb.OCROracleSpec = &os err = app.AddJobV2(context.Background(), &jb) require.NoError(t, err) diff --git a/core/web/presenters/eth_tx.go b/core/web/presenters/eth_tx.go index 955a1d7691e..88c35a504ae 100644 --- a/core/web/presenters/eth_tx.go +++ b/core/web/presenters/eth_tx.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -28,7 +28,7 @@ type EthTxResource struct { // GetName implements the api2go EntityNamer interface func (EthTxResource) GetName() string { - return "transactions" + return "evm_transactions" } // NewEthTxResource generates a EthTxResource from an Eth.Tx. @@ -36,7 +36,7 @@ func (EthTxResource) GetName() string { // For backwards compatibility, there is no id set when initializing from an // EthTx as the id being used was the EthTxAttempt Hash. // This should really use it's proper id -func NewEthTxResource(tx bulletprooftxmanager.EthTx) EthTxResource { +func NewEthTxResource(tx txmgr.EthTx) EthTxResource { return EthTxResource{ Data: hexutil.Bytes(tx.EncodedPayload), From: &tx.FromAddress, @@ -48,7 +48,7 @@ func NewEthTxResource(tx bulletprooftxmanager.EthTx) EthTxResource { } } -func NewEthTxResourceFromAttempt(txa bulletprooftxmanager.EthTxAttempt) EthTxResource { +func NewEthTxResourceFromAttempt(txa txmgr.EthTxAttempt) EthTxResource { tx := txa.EthTx r := NewEthTxResource(tx) diff --git a/core/web/presenters/eth_tx_test.go b/core/web/presenters/eth_tx_test.go index 46741883f0f..71c1bf29641 100644 --- a/core/web/presenters/eth_tx_test.go +++ b/core/web/presenters/eth_tx_test.go @@ -6,11 +6,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/assets" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/core/utils" ) func TestEthTxResource(t *testing.T) { @@ -18,13 +19,13 @@ func TestEthTxResource(t *testing.T) { from := common.HexToAddress("0x1") to := common.HexToAddress("0x2") - tx := bulletprooftxmanager.EthTx{ + tx := txmgr.EthTx{ ID: 1, EncodedPayload: []byte(`{"data": "is wilding out"}`), FromAddress: from, ToAddress: to, GasLimit: uint64(5000), - State: bulletprooftxmanager.EthTxConfirmed, + State: txmgr.EthTxConfirmed, Value: assets.NewEthValue(1), } @@ -36,7 +37,7 @@ func TestEthTxResource(t *testing.T) { expected := ` { "data": { - "type": "transactions", + "type": "evm_transactions", "id": "", "attributes": { "state": "confirmed", @@ -66,7 +67,7 @@ func TestEthTxResource(t *testing.T) { ) tx.Nonce = &nonce - txa := bulletprooftxmanager.EthTxAttempt{ + txa := txmgr.EthTxAttempt{ EthTx: tx, Hash: hash, GasPrice: gasPrice, @@ -82,7 +83,7 @@ func TestEthTxResource(t *testing.T) { expected = ` { "data": { - "type": "transactions", + "type": "evm_transactions", "id": "0x0000000000000000000000000000000000000000000000000000000000010203", "attributes": { "state": "confirmed", diff --git a/core/web/presenters/evm_chain.go b/core/web/presenters/evm_chain.go index 919e01996b1..10f209a03da 100644 --- a/core/web/presenters/evm_chain.go +++ b/core/web/presenters/evm_chain.go @@ -41,6 +41,7 @@ type EVMNodeResource struct { EVMChainID utils.Big `json:"evmChainID"` WSURL null.String `json:"wsURL"` HTTPURL null.String `json:"httpURL"` + State string `json:"state"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } @@ -58,6 +59,7 @@ func NewEVMNodeResource(node evmtypes.Node) EVMNodeResource { EVMChainID: node.EVMChainID, WSURL: node.WSURL, HTTPURL: node.HTTPURL, + State: node.State, CreatedAt: node.CreatedAt, UpdatedAt: node.UpdatedAt, } diff --git a/core/web/presenters/evm_forwarder.go b/core/web/presenters/evm_forwarder.go new file mode 100644 index 00000000000..7f627cc4667 --- /dev/null +++ b/core/web/presenters/evm_forwarder.go @@ -0,0 +1,34 @@ +package presenters + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/core/chains/evm/forwarders" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// EVMForwarderResource is an EVM forwarder JSONAPI resource. +type EVMForwarderResource struct { + JAID + Address common.Address `json:"address"` + EVMChainID utils.Big `json:"evmChainId"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// GetName implements the api2go EntityNamer interface +func (r EVMForwarderResource) GetName() string { + return "evm_forwarder" +} + +// NewEVMForwarderResource returns a new EVMForwarderResource for chain. +func NewEVMForwarderResource(fwd forwarders.Forwarder) EVMForwarderResource { + return EVMForwarderResource{ + JAID: NewJAIDInt64(fwd.ID), + Address: fwd.Address, + EVMChainID: fwd.EVMChainID, + CreatedAt: fwd.CreatedAt, + UpdatedAt: fwd.UpdatedAt, + } +} diff --git a/core/web/presenters/job.go b/core/web/presenters/job.go index e512f0bcb83..d8972401d48 100644 --- a/core/web/presenters/job.go +++ b/core/web/presenters/job.go @@ -3,18 +3,16 @@ package presenters import ( "time" - "github.com/smartcontractkit/chainlink/core/services/relay/types" - - "gopkg.in/guregu/null.v4" - "github.com/lib/pq" uuid "github.com/satori/go.uuid" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/core/services/pipeline" + "github.com/smartcontractkit/chainlink/core/services/relay/types" "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -147,8 +145,8 @@ type OffChainReportingSpec struct { } // NewOffChainReportingSpec initializes a new OffChainReportingSpec from a -// job.OffchainReportingOracleSpec -func NewOffChainReportingSpec(spec *job.OffchainReportingOracleSpec) *OffChainReportingSpec { +// job.OCROracleSpec +func NewOffChainReportingSpec(spec *job.OCROracleSpec) *OffChainReportingSpec { return &OffChainReportingSpec{ ContractAddress: spec.ContractAddress, P2PBootstrapPeers: spec.P2PBootstrapPeers, @@ -194,8 +192,8 @@ type OffChainReporting2Spec struct { } // NewOffChainReporting2Spec initializes a new OffChainReportingSpec from a -// job.OffchainReporting2OracleSpec -func NewOffChainReporting2Spec(spec *job.OffchainReporting2OracleSpec) *OffChainReporting2Spec { +// job.OCR2OracleSpec +func NewOffChainReporting2Spec(spec *job.OCR2OracleSpec) *OffChainReporting2Spec { return &OffChainReporting2Spec{ ContractID: spec.ContractID, Relay: spec.Relay, @@ -278,21 +276,21 @@ func NewCronSpec(spec *job.CronSpec) *CronSpec { } type VRFSpec struct { - CoordinatorAddress ethkey.EIP55Address `json:"coordinatorAddress"` - PublicKey secp256k1.PublicKey `json:"publicKey"` - FromAddress *ethkey.EIP55Address `json:"fromAddress"` - PollPeriod models.Duration `json:"pollPeriod"` - MinIncomingConfirmations uint32 `json:"confirmations"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - EVMChainID *utils.Big `json:"evmChainID"` + CoordinatorAddress ethkey.EIP55Address `json:"coordinatorAddress"` + PublicKey secp256k1.PublicKey `json:"publicKey"` + FromAddresses []ethkey.EIP55Address `json:"fromAddresses"` + PollPeriod models.Duration `json:"pollPeriod"` + MinIncomingConfirmations uint32 `json:"confirmations"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + EVMChainID *utils.Big `json:"evmChainID"` } func NewVRFSpec(spec *job.VRFSpec) *VRFSpec { return &VRFSpec{ CoordinatorAddress: spec.CoordinatorAddress, PublicKey: spec.PublicKey, - FromAddress: spec.FromAddress, + FromAddresses: spec.FromAddresses, PollPeriod: models.MustMakeDuration(spec.PollPeriod), MinIncomingConfirmations: spec.MinIncomingConfirmations, CreatedAt: spec.CreatedAt, @@ -419,9 +417,9 @@ func NewJobResource(j job.Job) *JobResource { case job.Cron: resource.CronSpec = NewCronSpec(j.CronSpec) case job.OffchainReporting: - resource.OffChainReportingSpec = NewOffChainReportingSpec(j.OffchainreportingOracleSpec) + resource.OffChainReportingSpec = NewOffChainReportingSpec(j.OCROracleSpec) case job.OffchainReporting2: - resource.OffChainReporting2Spec = NewOffChainReporting2Spec(j.Offchainreporting2OracleSpec) + resource.OffChainReporting2Spec = NewOffChainReporting2Spec(j.OCR2OracleSpec) case job.Keeper: resource.KeeperSpec = NewKeeperSpec(j.KeeperSpec) case job.VRF: diff --git a/core/web/presenters/job_test.go b/core/web/presenters/job_test.go index c9104317cbb..bf9c5343c5f 100644 --- a/core/web/presenters/job_test.go +++ b/core/web/presenters/job_test.go @@ -186,7 +186,7 @@ func TestJob(t *testing.T) { name: "ocr spec", job: job.Job{ ID: 1, - OffchainreportingOracleSpec: &job.OffchainReportingOracleSpec{ + OCROracleSpec: &job.OCROracleSpec{ ContractAddress: contractAddress, P2PBootstrapPeers: pq.StringArray{"/dns4/chain.link/tcp/1234/p2p/xxx"}, IsBootstrapPeer: true, diff --git a/core/web/presenters/terra_msg.go b/core/web/presenters/terra_msg.go new file mode 100644 index 00000000000..1d1174696d8 --- /dev/null +++ b/core/web/presenters/terra_msg.go @@ -0,0 +1,24 @@ +package presenters + +// TerraMsgResource repesents a Terra message JSONAPI resource. +type TerraMsgResource struct { + JAID + ChainID string + ContractID string + State string + TxHash *string +} + +// GetName implements the api2go EntityNamer interface +func (TerraMsgResource) GetName() string { + return "terra_messages" +} + +// NewTerraMsgResource returns a new partial TerraMsgResource. +func NewTerraMsgResource(id int64, chainID string, contractID string) TerraMsgResource { + return TerraMsgResource{ + JAID: NewJAIDInt64(id), + ChainID: chainID, + ContractID: contractID, + } +} diff --git a/core/web/replay_controller.go b/core/web/replay_controller.go index 7ec14c0bbbc..955e06b29c9 100644 --- a/core/web/replay_controller.go +++ b/core/web/replay_controller.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -23,6 +24,17 @@ func (bdc *ReplayController) ReplayFromBlock(c *gin.Context) { return } + // check if "force" query string parameter provided + var force bool + var err error + if fb := c.Query("force"); fb != "" { + force, err = strconv.ParseBool(fb) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Wrap(err, "boolean value required for 'force' query string param")) + return + } + } + blockNumber, err := strconv.ParseInt(c.Param("number"), 10, 0) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) @@ -46,7 +58,7 @@ func (bdc *ReplayController) ReplayFromBlock(c *gin.Context) { } chainID := chain.ID() - if err := bdc.App.ReplayFromBlock(chainID, uint64(blockNumber)); err != nil { + if err := bdc.App.ReplayFromBlock(chainID, uint64(blockNumber), force); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) return } diff --git a/core/web/resolver/bridge_test.go b/core/web/resolver/bridge_test.go index e0cc7d091f4..9948c785c9c 100644 --- a/core/web/resolver/bridge_test.go +++ b/core/web/resolver/bridge_test.go @@ -106,7 +106,7 @@ func Test_Bridge(t *testing.T) { } }` - name = bridges.TaskType("bridge1") + name = bridges.BridgeName("bridge1") ) bridgeURL, err := url.Parse("https://external.adapter") require.NoError(t, err) @@ -164,7 +164,7 @@ func Test_CreateBridge(t *testing.T) { t.Parallel() var ( - name = bridges.TaskType("bridge1") + name = bridges.BridgeName("bridge1") mutation = ` mutation createBridge($input: CreateBridgeInput!) { createBridge(input: $input) { @@ -244,7 +244,7 @@ func Test_UpdateBridge(t *testing.T) { t.Parallel() var ( - name = bridges.TaskType("bridge1") + name = bridges.BridgeName("bridge1") mutation = ` mutation updateBridge($id: ID!, $input: UpdateBridgeInput!) { updateBridge(id: $id, input: $input) { @@ -301,7 +301,7 @@ func Test_UpdateBridge(t *testing.T) { f.Mocks.bridgeORM.On("FindBridge", name).Return(bridge, nil) btr := &bridges.BridgeTypeRequest{ - Name: bridges.TaskType("bridge-updated"), + Name: bridges.BridgeName("bridge-updated"), URL: models.WebURL(*newBridgeURL), Confirmations: 2, MinimumContractPayment: assets.NewLinkFromJuels(2), @@ -361,7 +361,7 @@ func Test_UpdateBridge(t *testing.T) { func Test_DeleteBridgeMutation(t *testing.T) { t.Parallel() - name := bridges.TaskType("bridge1") + name := bridges.BridgeName("bridge1") bridgeURL, err := url.Parse("https://test-url.com") require.NoError(t, err) diff --git a/core/web/resolver/config_test.go b/core/web/resolver/config_test.go index 6d5029733d1..77cfcb62b0f 100644 --- a/core/web/resolver/config_test.go +++ b/core/web/resolver/config_test.go @@ -84,7 +84,9 @@ func TestResolver_Config(t *testing.T) { DefaultLogLevel: nil, LogFileDir: null.StringFrom("foo"), LogSQL: null.BoolFrom(true), - LogToDisk: null.BoolFrom(true), + LogFileMaxSize: null.StringFrom("100mb"), + LogFileMaxAge: null.IntFrom(3), + LogFileMaxBackups: null.IntFrom(12), OCRKeyBundleID: null.StringFrom("test"), OCRObservationTimeout: nil, OCRTransmitterAddress: nil, @@ -244,6 +246,10 @@ func TestResolver_Config(t *testing.T) { "key": "KEEPER_GAS_TIP_CAP_BUFFER_PERCENT", "value": "20" }, + { + "key": "KEEPER_BASE_FEE_BUFFER_PERCENT", + "value": "20" + }, { "key": "KEEPER_MAXIMUM_GRACE_PERIOD", "value": "0" @@ -293,8 +299,16 @@ func TestResolver_Config(t *testing.T) { "value": "true" }, { - "key": "LOG_TO_DISK", - "value": "true" + "key": "LOG_FILE_MAX_SIZE", + "value": "100.00mb" + }, + { + "key": "LOG_FILE_MAX_AGE", + "value": "3" + }, + { + "key": "LOG_FILE_MAX_BACKUPS", + "value": "12" }, { "key": "TRIGGER_FALLBACK_DB_POLL_INTERVAL", diff --git a/core/web/resolver/eth_key_test.go b/core/web/resolver/eth_key_test.go index d0da61ac40b..03e15da2605 100644 --- a/core/web/resolver/eth_key_test.go +++ b/core/web/resolver/eth_key_test.go @@ -39,10 +39,14 @@ func TestResolver_ETHKeys(t *testing.T) { }` address := ethkey.EIP55Address("0x5431F5F973781809D18643b87B44921b11355d81") + secondAddress := ethkey.EIP55Address("0x1438087186fdbfd4c256fa2df446921e30e54df8") keys := []ethkey.KeyV2{ { Address: address, }, + { + Address: secondAddress, + }, } gError := errors.New("error") keysError := fmt.Errorf("error getting unlocked keys: %v", gError) @@ -63,8 +67,16 @@ func TestResolver_ETHKeys(t *testing.T) { CreatedAt: f.Timestamp(), UpdatedAt: f.Timestamp(), }, + { + Address: secondAddress, + EVMChainID: *utils.NewBigI(42), + IsFunding: false, + CreatedAt: f.Timestamp(), + UpdatedAt: f.Timestamp(), + }, } chainID := *utils.NewBigI(12) + chainID2 := *utils.NewBigI(42) linkAddr := common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81") f.Mocks.cfg.On("Dev").Return(true) @@ -72,18 +84,26 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.ethKs.On("GetAll").Return(keys, nil) f.Mocks.ethKs.On("GetStatesForKeys", keys).Return(states, nil) f.Mocks.ethKs.On("Get", keys[0].Address.Hex()).Return(keys[0], nil) + f.Mocks.ethKs.On("Get", keys[1].Address.Hex()).Return(keys[1], nil) f.Mocks.ethClient.On("GetLINKBalance", linkAddr, address.Address()).Return(assets.NewLinkFromJuels(12), nil) + f.Mocks.ethClient.On("GetLINKBalance", linkAddr, secondAddress.Address()).Return(assets.NewLinkFromJuels(100), nil) f.Mocks.balM.On("GetEthBalance", address.Address()).Return(assets.NewEth(1)) + f.Mocks.balM.On("GetEthBalance", secondAddress.Address()).Return(assets.NewEth(12)) f.Mocks.chain.On("Client").Return(f.Mocks.ethClient) f.Mocks.chain.On("BalanceMonitor").Return(f.Mocks.balM) f.Mocks.scfg.On("LinkContractAddress").Return("0x5431F5F973781809D18643b87B44921b11355d81") f.Mocks.scfg.On("KeySpecificMaxGasPriceWei", keys[0].Address.Address()).Return(big.NewInt(1)) + f.Mocks.scfg.On("KeySpecificMaxGasPriceWei", keys[1].Address.Address()).Return(big.NewInt(1)) f.Mocks.chain.On("Config").Return(f.Mocks.scfg) f.Mocks.chainSet.On("Get", states[0].EVMChainID.ToInt()).Return(f.Mocks.chain, nil) - f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{chainID}).Return([]types.Chain{ + f.Mocks.chainSet.On("Get", states[1].EVMChainID.ToInt()).Return(f.Mocks.chain, nil) + f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{chainID, chainID2}).Return([]types.Chain{ { ID: chainID, }, + { + ID: chainID2, + }, }, nil) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) f.App.On("GetKeyStore").Return(f.Mocks.keystore) @@ -95,6 +115,18 @@ func TestResolver_ETHKeys(t *testing.T) { { "ethKeys": { "results": [ + { + "address": "0x1438087186fdbfd4c256fa2df446921e30e54df8", + "isFunding": false, + "ethBalance": "0.000000000000000012", + "linkBalance": "100", + "maxGasPriceWei": "1", + "createdAt": "2021-01-01T00:00:00Z", + "updatedAt": "2021-01-01T00:00:00Z", + "chain": { + "id": "42" + } + }, { "address": "0x5431F5F973781809D18643b87B44921b11355d81", "isFunding": true, diff --git a/core/web/resolver/eth_transaction.go b/core/web/resolver/eth_transaction.go index dc2a5aa62d4..3ef5b97f55f 100644 --- a/core/web/resolver/eth_transaction.go +++ b/core/web/resolver/eth_transaction.go @@ -6,20 +6,20 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/graph-gophers/graphql-go" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/utils/stringutils" "github.com/smartcontractkit/chainlink/core/web/loader" ) type EthTransactionResolver struct { - tx bulletprooftxmanager.EthTx + tx txmgr.EthTx } -func NewEthTransaction(tx bulletprooftxmanager.EthTx) *EthTransactionResolver { +func NewEthTransaction(tx txmgr.EthTx) *EthTransactionResolver { return &EthTransactionResolver{tx: tx} } -func NewEthTransactions(results []bulletprooftxmanager.EthTx) []*EthTransactionResolver { +func NewEthTransactions(results []txmgr.EthTx) []*EthTransactionResolver { var resolver []*EthTransactionResolver for _, tx := range results { @@ -126,11 +126,11 @@ func (r *EthTransactionResolver) SentAt(ctx context.Context) *string { // -- EthTransaction Query -- type EthTransactionPayloadResolver struct { - tx *bulletprooftxmanager.EthTx + tx *txmgr.EthTx NotFoundErrorUnionType } -func NewEthTransactionPayload(tx *bulletprooftxmanager.EthTx, err error) *EthTransactionPayloadResolver { +func NewEthTransactionPayload(tx *txmgr.EthTx, err error) *EthTransactionPayloadResolver { e := NotFoundErrorUnionType{err: err, message: "transaction not found", isExpectedErrorFn: nil} return &EthTransactionPayloadResolver{tx: tx, NotFoundErrorUnionType: e} @@ -147,11 +147,11 @@ func (r *EthTransactionPayloadResolver) ToEthTransaction() (*EthTransactionResol // -- EthTransactions Query -- type EthTransactionsPayloadResolver struct { - results []bulletprooftxmanager.EthTx + results []txmgr.EthTx total int32 } -func NewEthTransactionsPayload(results []bulletprooftxmanager.EthTx, total int32) *EthTransactionsPayloadResolver { +func NewEthTransactionsPayload(results []txmgr.EthTx, total int32) *EthTransactionsPayloadResolver { return &EthTransactionsPayloadResolver{results: results, total: total} } diff --git a/core/web/resolver/eth_transaction_attempt.go b/core/web/resolver/eth_transaction_attempt.go index 418a197d7ae..a62b9cf8a2f 100644 --- a/core/web/resolver/eth_transaction_attempt.go +++ b/core/web/resolver/eth_transaction_attempt.go @@ -3,19 +3,19 @@ package resolver import ( "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/utils/stringutils" ) type EthTransactionAttemptResolver struct { - attmpt bulletprooftxmanager.EthTxAttempt + attmpt txmgr.EthTxAttempt } -func NewEthTransactionAttempt(attmpt bulletprooftxmanager.EthTxAttempt) *EthTransactionAttemptResolver { +func NewEthTransactionAttempt(attmpt txmgr.EthTxAttempt) *EthTransactionAttemptResolver { return &EthTransactionAttemptResolver{attmpt: attmpt} } -func NewEthTransactionsAttempts(results []bulletprooftxmanager.EthTxAttempt) []*EthTransactionAttemptResolver { +func NewEthTransactionsAttempts(results []txmgr.EthTxAttempt) []*EthTransactionAttemptResolver { var resolver []*EthTransactionAttemptResolver for _, tx := range results { @@ -50,11 +50,11 @@ func (r *EthTransactionAttemptResolver) SentAt() *string { // -- EthTransactionAttempts Query -- type EthTransactionsAttemptsPayloadResolver struct { - results []bulletprooftxmanager.EthTxAttempt + results []txmgr.EthTxAttempt total int32 } -func NewEthTransactionsAttemptsPayload(results []bulletprooftxmanager.EthTxAttempt, total int32) *EthTransactionsAttemptsPayloadResolver { +func NewEthTransactionsAttemptsPayload(results []txmgr.EthTxAttempt, total int32) *EthTransactionsAttemptsPayloadResolver { return &EthTransactionsAttemptsPayloadResolver{results: results, total: total} } diff --git a/core/web/resolver/eth_transaction_test.go b/core/web/resolver/eth_transaction_test.go index 4398346c9b3..9e24d3919ee 100644 --- a/core/web/resolver/eth_transaction_test.go +++ b/core/web/resolver/eth_transaction_test.go @@ -9,7 +9,7 @@ import ( gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" + "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/utils" ) @@ -59,18 +59,18 @@ func TestResolver_EthTransaction(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("FindEthTxByHash", hash).Return(&bulletprooftxmanager.EthTx{ + f.Mocks.txmORM.On("FindEthTxByHash", hash).Return(&txmgr.EthTx{ ID: 1, ToAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), FromAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), - State: bulletprooftxmanager.EthTxInProgress, + State: txmgr.EthTxInProgress, EncodedPayload: []byte("encoded payload"), GasLimit: 100, Value: assets.NewEthValue(100), EVMChainID: *utils.NewBigI(22), Nonce: nil, }, nil) - f.Mocks.bptxmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]bulletprooftxmanager.EthTxAttempt{ + f.Mocks.txmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]txmgr.EthTxAttempt{ { EthTxID: 1, Hash: hash, @@ -79,7 +79,7 @@ func TestResolver_EthTransaction(t *testing.T) { BroadcastBeforeBlockNum: nil, }, }, nil) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.App.On("TxmORM").Return(f.Mocks.txmORM) f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{chainID}).Return([]types.Chain{ { ID: chainID, @@ -119,18 +119,18 @@ func TestResolver_EthTransaction(t *testing.T) { before: func(f *gqlTestFramework) { num := int64(2) - f.Mocks.bptxmORM.On("FindEthTxByHash", hash).Return(&bulletprooftxmanager.EthTx{ + f.Mocks.txmORM.On("FindEthTxByHash", hash).Return(&txmgr.EthTx{ ID: 1, ToAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), FromAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), - State: bulletprooftxmanager.EthTxInProgress, + State: txmgr.EthTxInProgress, EncodedPayload: []byte("encoded payload"), GasLimit: 100, Value: assets.NewEthValue(100), EVMChainID: *utils.NewBigI(22), Nonce: &num, }, nil) - f.Mocks.bptxmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]bulletprooftxmanager.EthTxAttempt{ + f.Mocks.txmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]txmgr.EthTxAttempt{ { EthTxID: 1, Hash: hash, @@ -139,7 +139,7 @@ func TestResolver_EthTransaction(t *testing.T) { BroadcastBeforeBlockNum: &num, }, }, nil) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.App.On("TxmORM").Return(f.Mocks.txmORM) f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{chainID}).Return([]types.Chain{ { ID: chainID, @@ -177,8 +177,8 @@ func TestResolver_EthTransaction(t *testing.T) { name: "not found error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("FindEthTxByHash", hash).Return(nil, sql.ErrNoRows) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.Mocks.txmORM.On("FindEthTxByHash", hash).Return(nil, sql.ErrNoRows) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, variables: variables, @@ -194,8 +194,8 @@ func TestResolver_EthTransaction(t *testing.T) { name: "generic error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("FindEthTxByHash", hash).Return(nil, gError) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.Mocks.txmORM.On("FindEthTxByHash", hash).Return(nil, gError) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, variables: variables, @@ -250,19 +250,19 @@ func TestResolver_EthTransactions(t *testing.T) { before: func(f *gqlTestFramework) { num := int64(2) - f.Mocks.bptxmORM.On("EthTransactions", PageDefaultOffset, PageDefaultLimit).Return([]bulletprooftxmanager.EthTx{ + f.Mocks.txmORM.On("EthTransactions", PageDefaultOffset, PageDefaultLimit).Return([]txmgr.EthTx{ { ID: 1, ToAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), FromAddress: common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81"), - State: bulletprooftxmanager.EthTxInProgress, + State: txmgr.EthTxInProgress, EncodedPayload: []byte("encoded payload"), GasLimit: 100, Value: assets.NewEthValue(100), EVMChainID: *utils.NewBigI(22), }, }, 1, nil) - f.Mocks.bptxmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]bulletprooftxmanager.EthTxAttempt{ + f.Mocks.txmORM.On("FindEthTxAttemptsByEthTxIDs", []int64{1}).Return([]txmgr.EthTxAttempt{ { EthTxID: 1, Hash: hash, @@ -271,7 +271,7 @@ func TestResolver_EthTransactions(t *testing.T) { BroadcastBeforeBlockNum: &num, }, }, nil) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, result: ` @@ -301,8 +301,8 @@ func TestResolver_EthTransactions(t *testing.T) { name: "generic error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("EthTransactions", PageDefaultOffset, PageDefaultLimit).Return(nil, 0, gError) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.Mocks.txmORM.On("EthTransactions", PageDefaultOffset, PageDefaultLimit).Return(nil, 0, gError) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, result: `null`, @@ -348,16 +348,16 @@ func TestResolver_EthTransactionsAttempts(t *testing.T) { before: func(f *gqlTestFramework) { num := int64(2) - f.Mocks.bptxmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return([]bulletprooftxmanager.EthTxAttempt{ + f.Mocks.txmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return([]txmgr.EthTxAttempt{ { Hash: hash, GasPrice: utils.NewBigI(12), SignedRawTx: []byte("something"), BroadcastBeforeBlockNum: &num, - EthTx: bulletprooftxmanager.EthTx{}, + EthTx: txmgr.EthTx{}, }, }, 1, nil) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, result: ` @@ -379,7 +379,7 @@ func TestResolver_EthTransactionsAttempts(t *testing.T) { name: "success with nil values", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return([]bulletprooftxmanager.EthTxAttempt{ + f.Mocks.txmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return([]txmgr.EthTxAttempt{ { Hash: hash, GasPrice: utils.NewBigI(12), @@ -387,7 +387,7 @@ func TestResolver_EthTransactionsAttempts(t *testing.T) { BroadcastBeforeBlockNum: nil, }, }, 1, nil) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, result: ` @@ -409,8 +409,8 @@ func TestResolver_EthTransactionsAttempts(t *testing.T) { name: "generic error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.bptxmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return(nil, 0, gError) - f.App.On("BPTXMORM").Return(f.Mocks.bptxmORM) + f.Mocks.txmORM.On("EthTxAttempts", PageDefaultOffset, PageDefaultLimit).Return(nil, 0, gError) + f.App.On("TxmORM").Return(f.Mocks.txmORM) }, query: query, result: `null`, diff --git a/core/web/resolver/evm_chain_test.go b/core/web/resolver/evm_chain_test.go index 02f4b34b305..7676c4308dc 100644 --- a/core/web/resolver/evm_chain_test.go +++ b/core/web/resolver/evm_chain_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/mock" + "github.com/ethereum/go-ethereum/common" gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/pkg/errors" @@ -95,7 +97,8 @@ func TestResolver_Chains(t *testing.T) { }, }, }, 1, nil) - f.Mocks.evmORM.On("GetNodesByChainIDs", []utils.Big{chainID}). + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) + f.Mocks.chainSet.On("GetNodesByChainIDs", mock.Anything, []utils.Big{chainID}). Return([]types.Node{ { ID: nodeID, @@ -193,6 +196,7 @@ func TestResolver_Chain(t *testing.T) { require.NoError(t, err) f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) f.Mocks.evmORM.On("Chain", chainID).Return(types.Chain{ ID: chainID, Enabled: true, @@ -213,7 +217,7 @@ func TestResolver_Chain(t *testing.T) { }, }, }, nil) - f.Mocks.evmORM.On("GetNodesByChainIDs", []utils.Big{chainID}). + f.Mocks.chainSet.On("GetNodesByChainIDs", mock.Anything, []utils.Big{chainID}). Return([]types.Node{ { ID: nodeID, @@ -383,7 +387,7 @@ func TestResolver_CreateChain(t *testing.T) { }, } - f.Mocks.chainSet.On("Add", big.NewInt(1233), cfg).Return(types.Chain{ + f.Mocks.chainSet.On("Add", mock.Anything, big.NewInt(1233), cfg).Return(types.Chain{ ID: *utils.NewBigI(1), Enabled: true, CreatedAt: f.Timestamp(), @@ -458,7 +462,7 @@ func TestResolver_CreateChain(t *testing.T) { }, } - f.Mocks.chainSet.On("Add", big.NewInt(1233), cfg).Return(types.Chain{ + f.Mocks.chainSet.On("Add", mock.Anything, big.NewInt(1233), cfg).Return(types.Chain{ ID: *utils.NewBigI(1), Enabled: true, CreatedAt: f.Timestamp(), @@ -691,7 +695,7 @@ func TestResolver_UpdateChain(t *testing.T) { }, } - f.Mocks.chainSet.On("Configure", chainID.ToInt(), true, cfg).Return(types.Chain{ + f.Mocks.chainSet.On("Configure", mock.Anything, chainID.ToInt(), true, cfg).Return(types.Chain{ ID: chainID, Enabled: true, CreatedAt: f.Timestamp(), @@ -766,7 +770,7 @@ func TestResolver_UpdateChain(t *testing.T) { }, } - f.Mocks.chainSet.On("Configure", chainID.ToInt(), true, cfg).Return(types.Chain{}, sql.ErrNoRows) + f.Mocks.chainSet.On("Configure", mock.Anything, chainID.ToInt(), true, cfg).Return(types.Chain{}, sql.ErrNoRows) f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: mutation, @@ -799,7 +803,7 @@ func TestResolver_UpdateChain(t *testing.T) { }, } - f.Mocks.chainSet.On("Configure", chainID.ToInt(), true, cfg).Return(types.Chain{}, gError) + f.Mocks.chainSet.On("Configure", mock.Anything, chainID.ToInt(), true, cfg).Return(types.Chain{}, gError) f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: mutation, diff --git a/core/web/resolver/feeds_manager.go b/core/web/resolver/feeds_manager.go index ad60d61c346..48f07e9e572 100644 --- a/core/web/resolver/feeds_manager.go +++ b/core/web/resolver/feeds_manager.go @@ -20,12 +20,14 @@ func ToJobType(s string) (JobType, error) { return JobTypeFluxMonitor, nil case "ocr": return JobTypeOCR, nil + case "ocr2": + return JobTypeOCR2, nil default: return "", errors.New("invalid job type") } } -// FromJobTypeInput converts a JoyType into a string which is used to create/update +// FromJobTypeInput converts a JobType into a string which is used to create/update // a feeds manager. // // FluxMonitor is a special case because the FeedsManager expects a 'fluxmonitor' @@ -46,6 +48,7 @@ func FromJobTypeInput(jt JobType) string { const ( JobTypeFluxMonitor JobType = "FLUX_MONITOR" JobTypeOCR JobType = "OCR" + JobTypeOCR2 JobType = "OCR2" ) // FeedsManagerResolver resolves the FeedsManager type. diff --git a/core/web/resolver/feeds_manager_test.go b/core/web/resolver/feeds_manager_test.go index 326fce9f855..7a8ca2e92bb 100644 --- a/core/web/resolver/feeds_manager_test.go +++ b/core/web/resolver/feeds_manager_test.go @@ -250,7 +250,7 @@ func Test_CreateFeedsManager(t *testing.T) { "input": map[string]interface{}{ "name": name, "uri": uri, - "jobTypes": []interface{}{"FLUX_MONITOR"}, + "jobTypes": []interface{}{"FLUX_MONITOR", "OCR2"}, "publicKey": pubKeyHex, "isBootstrapPeer": false, }, @@ -270,7 +270,7 @@ func Test_CreateFeedsManager(t *testing.T) { Name: name, URI: uri, PublicKey: *pubKey, - JobTypes: pq.StringArray([]string{"fluxmonitor"}), + JobTypes: pq.StringArray([]string{"fluxmonitor", "ocr2"}), IsOCRBootstrapPeer: false, OCRBootstrapPeerMultiaddr: null.StringFromPtr(nil), }).Return(mgrID, nil) @@ -279,7 +279,7 @@ func Test_CreateFeedsManager(t *testing.T) { Name: name, URI: uri, PublicKey: *pubKey, - JobTypes: []string{"fluxmonitor"}, + JobTypes: []string{"fluxmonitor", "ocr2"}, IsOCRBootstrapPeer: false, OCRBootstrapPeerMultiaddr: null.StringFromPtr(nil), IsConnectionActive: false, @@ -296,7 +296,7 @@ func Test_CreateFeedsManager(t *testing.T) { "name": "manager1", "uri": "localhost:2000", "publicKey": "3b0f149627adb7b6fafe1497a9dfc357f22295a5440786c3bc566dfdb0176808", - "jobTypes": ["FLUX_MONITOR"], + "jobTypes": ["FLUX_MONITOR", "OCR2"], "isBootstrapPeer": false, "bootstrapPeerMultiaddr": null, "isConnectionActive": false, diff --git a/core/web/resolver/helpers.go b/core/web/resolver/helpers.go index 5bc6befdcde..c7572c67c95 100644 --- a/core/web/resolver/helpers.go +++ b/core/web/resolver/helpers.go @@ -57,7 +57,7 @@ func ValidateBridgeTypeUniqueness(bt *bridges.BridgeTypeRequest, orm bridges.ORM if err == nil { return fmt.Errorf("bridge type %v already exists", bt.Name) } - if err != nil && err != sql.ErrNoRows { + if err != nil && !errors.Is(err, sql.ErrNoRows) { return fmt.Errorf("error determining if bridge type %v already exists", bt.Name) } @@ -73,7 +73,7 @@ func ValidateBridgeType(bt *bridges.BridgeTypeRequest) error { if len(bt.Name.String()) < 1 { return errors.New("No name specified") } - if _, err := bridges.NewTaskType(bt.Name.String()); err != nil { + if _, err := bridges.ParseBridgeName(bt.Name.String()); err != nil { return errors.Wrap(err, "invalid bridge name") } u := bt.URL.String() diff --git a/core/web/resolver/job_proposal.go b/core/web/resolver/job_proposal.go index 235f7a87ef7..d0ef05c572a 100644 --- a/core/web/resolver/job_proposal.go +++ b/core/web/resolver/job_proposal.go @@ -80,6 +80,22 @@ func (r *JobProposalResolver) ExternalJobID() *string { return nil } +// JobID resolves to the job proposal JobID if it has an ExternalJobID +func (r *JobProposalResolver) JobID(ctx context.Context) (*string, error) { + if !r.jp.ExternalJobID.Valid { + return nil, nil + } + + job, err := loader.GetJobByExternalJobID(ctx, r.jp.ExternalJobID.UUID.String()) + if err != nil { + return nil, err + } + + id := strconv.FormatInt(int64(job.ID), 10) + + return &id, err +} + // FeedsManager resolves the job proposal's feeds manager object field. func (r *JobProposalResolver) FeedsManager(ctx context.Context) (*FeedsManagerResolver, error) { mgr, err := loader.GetFeedsManagerByID(ctx, strconv.FormatInt(r.jp.FeedsManagerID, 10)) diff --git a/core/web/resolver/job_test.go b/core/web/resolver/job_test.go index aa26caca0cc..39feb6134d2 100644 --- a/core/web/resolver/job_test.go +++ b/core/web/resolver/job_test.go @@ -69,15 +69,15 @@ func TestResolver_Jobs(t *testing.T) { f.App.On("JobORM").Return(f.Mocks.jobORM) f.Mocks.jobORM.On("FindJobs", 0, 50).Return([]job.Job{ { - ID: 1, - Name: null.StringFrom("job1"), - SchemaVersion: 1, - MaxTaskDuration: models.Interval(1 * time.Second), - ExternalJobID: externalJobID, - CreatedAt: f.Timestamp(), - Type: job.OffchainReporting, - PipelineSpecID: plnSpecID, - OffchainreportingOracleSpec: &job.OffchainReportingOracleSpec{}, + ID: 1, + Name: null.StringFrom("job1"), + SchemaVersion: 1, + MaxTaskDuration: models.Interval(1 * time.Second), + ExternalJobID: externalJobID, + CreatedAt: f.Timestamp(), + Type: job.OffchainReporting, + PipelineSpecID: plnSpecID, + OCROracleSpec: &job.OCROracleSpec{}, PipelineSpec: &pipeline.Spec{ DotDagSource: "ds1 [type=bridge name=voter_turnout];", }, @@ -175,14 +175,14 @@ func TestResolver_Job(t *testing.T) { before: func(f *gqlTestFramework) { f.App.On("JobORM").Return(f.Mocks.jobORM) f.Mocks.jobORM.On("FindJobTx", id).Return(job.Job{ - ID: 1, - Name: null.StringFrom("job1"), - SchemaVersion: 1, - MaxTaskDuration: models.Interval(1 * time.Second), - ExternalJobID: externalJobID, - CreatedAt: f.Timestamp(), - Type: job.OffchainReporting, - OffchainreportingOracleSpec: &job.OffchainReportingOracleSpec{}, + ID: 1, + Name: null.StringFrom("job1"), + SchemaVersion: 1, + MaxTaskDuration: models.Interval(1 * time.Second), + ExternalJobID: externalJobID, + CreatedAt: f.Timestamp(), + Type: job.OffchainReporting, + OCROracleSpec: &job.OCROracleSpec{}, PipelineSpec: &pipeline.Spec{ DotDagSource: "ds1 [type=bridge name=voter_turnout];", }, diff --git a/core/web/resolver/mutation.go b/core/web/resolver/mutation.go index 2430842fa9e..ce7cff4fbf8 100644 --- a/core/web/resolver/mutation.go +++ b/core/web/resolver/mutation.go @@ -10,7 +10,6 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/lib/pq" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "go.uber.org/zap/zapcore" "gopkg.in/guregu/null.v4" @@ -33,8 +32,9 @@ import ( "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ocrkey" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/vrfkey" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting2" + "github.com/smartcontractkit/chainlink/core/services/ocr" + "github.com/smartcontractkit/chainlink/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/core/services/vrf" "github.com/smartcontractkit/chainlink/core/services/webhook" "github.com/smartcontractkit/chainlink/core/store/models" @@ -75,7 +75,7 @@ func (r *Resolver) CreateBridge(ctx context.Context, args struct{ Input createBr } btr := &bridges.BridgeTypeRequest{ - Name: bridges.TaskType(args.Input.Name), + Name: bridges.BridgeName(args.Input.Name), URL: webURL, Confirmations: uint32(args.Input.Confirmations), MinimumContractPayment: minContractPayment, @@ -224,13 +224,13 @@ func (r *Resolver) UpdateBridge(ctx context.Context, args struct { } btr := &bridges.BridgeTypeRequest{ - Name: bridges.TaskType(args.Input.Name), + Name: bridges.BridgeName(args.Input.Name), URL: webURL, Confirmations: uint32(args.Input.Confirmations), MinimumContractPayment: minContractPayment, } - taskType, err := bridges.NewTaskType(string(args.ID)) + taskType, err := bridges.ParseBridgeName(string(args.ID)) if err != nil { return nil, err } @@ -382,7 +382,7 @@ func (r *Resolver) DeleteNode(ctx context.Context, args struct { return nil, err } - node, err := r.App.EVMORM().Node(id) + node, err := r.App.GetChains().EVM.GetNode(ctx, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return NewDeleteNodePayloadResolver(nil, err), nil @@ -413,7 +413,7 @@ func (r *Resolver) DeleteBridge(ctx context.Context, args struct { return nil, err } - taskType, err := bridges.NewTaskType(string(args.ID)) + taskType, err := bridges.ParseBridgeName(string(args.ID)) if err != nil { return NewDeleteBridgePayload(nil, err), nil } @@ -501,7 +501,7 @@ func (r *Resolver) DeleteVRFKey(ctx context.Context, args struct { key, err := r.App.GetKeyStore().VRF().Delete(string(args.ID)) if err != nil { - if errors.Cause(err) == keystore.ErrMissingVRFKey { + if errors.Is(errors.Cause(err), keystore.ErrMissingVRFKey) { return NewDeleteVRFKeyPayloadResolver(vrfkey.KeyV2{}, err), nil } return nil, err @@ -844,7 +844,7 @@ func (r *Resolver) CreateChain(ctx context.Context, args struct { chainCfg.KeySpecific = sCfgs } - chain, err := r.App.GetChains().EVM.Add(id.ToInt(), *chainCfg) + chain, err := r.App.GetChains().EVM.Add(ctx, id.ToInt(), *chainCfg) if err != nil { return nil, err } @@ -892,7 +892,7 @@ func (r *Resolver) UpdateChain(ctx context.Context, args struct { chainCfg.KeySpecific = sCfgs } - chain, err := r.App.GetChains().EVM.Configure(id.ToInt(), args.Input.Enabled, *chainCfg) + chain, err := r.App.GetChains().EVM.Configure(ctx, id.ToInt(), args.Input.Enabled, *chainCfg) if err != nil { if errors.Is(err, sql.ErrNoRows) { return NewUpdateChainPayload(nil, nil, err), nil @@ -954,12 +954,12 @@ func (r *Resolver) CreateJob(ctx context.Context, args struct { config := r.App.GetConfig() switch jbt { case job.OffchainReporting: - jb, err = offchainreporting.ValidatedOracleSpecToml(r.App.GetChains().EVM, args.Input.TOML) + jb, err = ocr.ValidatedOracleSpecToml(r.App.GetChains().EVM, args.Input.TOML) if !config.Dev() && !config.FeatureOffchainReporting() { return nil, errors.New("The Offchain Reporting feature is disabled by configuration") } case job.OffchainReporting2: - jb, err = offchainreporting2.ValidatedOracleSpecToml(r.App.GetConfig(), args.Input.TOML) + jb, err = validate.ValidatedOracleSpecToml(r.App.GetConfig(), args.Input.TOML) if !config.Dev() && !config.FeatureOffchainReporting2() { return nil, errors.New("The Offchain Reporting 2 feature is disabled by configuration") } diff --git a/core/web/resolver/node.go b/core/web/resolver/node.go index edfea99b320..f04c40a4c4f 100644 --- a/core/web/resolver/node.go +++ b/core/web/resolver/node.go @@ -47,6 +47,11 @@ func (r *NodeResolver) HTTPURL() string { return r.node.HTTPURL.String } +// State resolves the node state +func (r *NodeResolver) State() string { + return r.node.State +} + // Chain resolves the node's chain object field. func (r *NodeResolver) Chain(ctx context.Context) (*ChainResolver, error) { chain, err := loader.GetChainByID(ctx, r.node.EVMChainID.String()) diff --git a/core/web/resolver/node_test.go b/core/web/resolver/node_test.go index 7f24a542b5b..c84d8969843 100644 --- a/core/web/resolver/node_test.go +++ b/core/web/resolver/node_test.go @@ -8,9 +8,11 @@ import ( gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/utils/stringutils" ) @@ -47,7 +49,8 @@ func TestResolver_Nodes(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Nodes", PageDefaultOffset, PageDefaultLimit).Return([]types.Node{ + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) + f.Mocks.chainSet.On("GetNodes", mock.Anything, PageDefaultOffset, PageDefaultLimit).Return([]types.Node{ { ID: nodeID, Name: "node-name", @@ -55,12 +58,12 @@ func TestResolver_Nodes(t *testing.T) { CreatedAt: f.Timestamp(), }, }, 1, nil) + f.App.On("EVMORM").Return(f.Mocks.evmORM) f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{chainID}).Return([]types.Chain{ { ID: chainID, }, }, nil) - f.App.On("EVMORM").Return(f.Mocks.evmORM) }, query: query, result: ` @@ -84,8 +87,8 @@ func TestResolver_Nodes(t *testing.T) { name: "generic error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Nodes", PageDefaultOffset, PageDefaultLimit).Return([]types.Node{}, 0, gError) - f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.Mocks.chainSet.On("GetNodes", mock.Anything, PageDefaultOffset, PageDefaultLimit).Return([]types.Node{}, 0, gError) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: query, result: `null`, @@ -129,13 +132,13 @@ func Test_NodeQuery(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Node", nodeID).Return(types.Node{ + f.Mocks.chainSet.On("GetNode", mock.Anything, nodeID).Return(types.Node{ ID: nodeID, Name: "node-name", WSURL: null.StringFrom("ws://some-url"), HTTPURL: null.StringFrom("http://some-url"), }, nil) - f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: query, result: ` @@ -151,8 +154,8 @@ func Test_NodeQuery(t *testing.T) { name: "not found error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Node", int32(200)).Return(types.Node{}, sql.ErrNoRows) - f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.Mocks.chainSet.On("GetNode", mock.Anything, int32(200)).Return(types.Node{}, sql.ErrNoRows) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: query, result: ` @@ -210,6 +213,7 @@ func Test_CreateNodeMutation(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { + f.App.On("EVMORM").Return(f.Mocks.evmORM) f.Mocks.evmORM.On("CreateNode", createNodeInput).Return(types.Node{ ID: int32(1), Name: createNodeInput.Name, @@ -221,7 +225,6 @@ func Test_CreateNodeMutation(t *testing.T) { f.Mocks.evmORM.On("GetChainsByIDs", []utils.Big{createNodeInput.EVMChainID}).Return([]types.Chain{ {ID: *utils.NewBigI(1), Enabled: true}, }, nil) - f.App.On("EVMORM").Return(f.Mocks.evmORM) }, query: mutation, variables: input, @@ -298,9 +301,10 @@ func Test_DeleteNodeMutation(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Node", fakeID).Return(fakeNode, nil) - f.Mocks.evmORM.On("DeleteNode", int64(2)).Return(nil) f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.Mocks.chainSet.On("GetNode", mock.Anything, fakeID).Return(fakeNode, nil) + f.Mocks.evmORM.On("DeleteNode", int64(2)).Return(nil) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: mutation, variables: variables, @@ -310,8 +314,8 @@ func Test_DeleteNodeMutation(t *testing.T) { name: "not found error on fetch", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Node", fakeID).Return(types.Node{}, sql.ErrNoRows) - f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.Mocks.chainSet.On("GetNode", mock.Anything, fakeID).Return(types.Node{}, sql.ErrNoRows) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: mutation, variables: variables, @@ -327,9 +331,10 @@ func Test_DeleteNodeMutation(t *testing.T) { name: "not found error on delete", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.evmORM.On("Node", fakeID).Return(fakeNode, nil) - f.Mocks.evmORM.On("DeleteNode", int64(2)).Return(sql.ErrNoRows) f.App.On("EVMORM").Return(f.Mocks.evmORM) + f.Mocks.chainSet.On("GetNode", mock.Anything, fakeID).Return(fakeNode, nil) + f.Mocks.evmORM.On("DeleteNode", int64(2)).Return(sql.ErrNoRows) + f.App.On("GetChains").Return(chainlink.Chains{EVM: f.Mocks.chainSet}) }, query: mutation, variables: variables, diff --git a/core/web/resolver/query.go b/core/web/resolver/query.go index b426a102a66..2175201cb55 100644 --- a/core/web/resolver/query.go +++ b/core/web/resolver/query.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/graph-gophers/graphql-go" @@ -25,7 +26,7 @@ func (r *Resolver) Bridge(ctx context.Context, args struct{ ID graphql.ID }) (*B return nil, err } - name, err := bridges.NewTaskType(string(args.ID)) + name, err := bridges.ParseBridgeName(string(args.ID)) if err != nil { return nil, err } @@ -232,7 +233,7 @@ func (r *Resolver) Node(ctx context.Context, args struct{ ID graphql.ID }) (*Nod return nil, err } - node, err := r.App.EVMORM().Node(id) + node, err := r.App.GetChains().EVM.GetNode(ctx, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return NewNodePayloadResolver(nil, err), nil @@ -280,7 +281,7 @@ func (r *Resolver) VRFKey(ctx context.Context, args struct { key, err := r.App.GetKeyStore().VRF().Get(string(args.ID)) if err != nil { - if errors.Cause(err) == keystore.ErrMissingVRFKey { + if errors.Is(errors.Cause(err), keystore.ErrMissingVRFKey) { return NewVRFKeyPayloadResolver(vrfkey.KeyV2{}, err), nil } return nil, err @@ -326,7 +327,7 @@ func (r *Resolver) Nodes(ctx context.Context, args struct { offset := pageOffset(args.Offset) limit := pageLimit(args.Limit) - nodes, count, err := r.App.EVMORM().Nodes(offset, limit) + nodes, count, err := r.App.GetChains().EVM.GetNodes(ctx, offset, limit) if err != nil { return nil, err } @@ -409,7 +410,7 @@ func (r *Resolver) ETHKeys(ctx context.Context) (*ETHKeysPayloadResolver, error) } chain, err := r.App.GetChains().EVM.Get(state.EVMChainID.ToInt()) - if errors.Cause(err) == evm.ErrNoChains { + if errors.Is(errors.Cause(err), evm.ErrNoChains) { ethKeys = append(ethKeys, ETHKey{ addr: k.Address, state: state, @@ -428,6 +429,11 @@ func (r *Resolver) ETHKeys(ctx context.Context) (*ETHKeysPayloadResolver, error) }) } + // Put funding keys to the end + sort.SliceStable(ethKeys, func(i, j int) bool { + return !ethKeys[i].state.IsFunding && ethKeys[j].state.IsFunding + }) + return NewETHKeysPayload(ethKeys), nil } @@ -451,7 +457,7 @@ func (r *Resolver) EthTransaction(ctx context.Context, args struct { } hash := common.HexToHash(string(args.Hash)) - etx, err := r.App.BPTXMORM().FindEthTxByHash(hash) + etx, err := r.App.TxmORM().FindEthTxByHash(hash) if err != nil { if errors.Is(err, sql.ErrNoRows) { return NewEthTransactionPayload(nil, err), nil @@ -474,7 +480,7 @@ func (r *Resolver) EthTransactions(ctx context.Context, args struct { offset := pageOffset(args.Offset) limit := pageLimit(args.Limit) - txs, count, err := r.App.BPTXMORM().EthTransactions(offset, limit) + txs, count, err := r.App.TxmORM().EthTransactions(offset, limit) if err != nil { return nil, err } @@ -493,7 +499,7 @@ func (r *Resolver) EthTransactionsAttempts(ctx context.Context, args struct { offset := pageOffset(args.Offset) limit := pageLimit(args.Limit) - attempts, count, err := r.App.BPTXMORM().EthTxAttempts(offset, limit) + attempts, count, err := r.App.TxmORM().EthTxAttempts(offset, limit) if err != nil { return nil, err } diff --git a/core/web/resolver/resolver_test.go b/core/web/resolver/resolver_test.go index 7bb2b5c2d86..2854a233bc8 100644 --- a/core/web/resolver/resolver_test.go +++ b/core/web/resolver/resolver_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/mock" bridgeORMMocks "github.com/smartcontractkit/chainlink/core/bridges/mocks" - bulletprooftxmanagerMocks "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager/mocks" evmConfigMocks "github.com/smartcontractkit/chainlink/core/chains/evm/config/mocks" evmORMMocks "github.com/smartcontractkit/chainlink/core/chains/evm/mocks" + txmgrMocks "github.com/smartcontractkit/chainlink/core/chains/evm/txmgr/mocks" configMocks "github.com/smartcontractkit/chainlink/core/config/mocks" coremocks "github.com/smartcontractkit/chainlink/core/internal/mocks" feedsMocks "github.com/smartcontractkit/chainlink/core/services/feeds/mocks" @@ -50,7 +50,7 @@ type mocks struct { ethClient *evmORMMocks.Client eIMgr *webhookmocks.ExternalInitiatorManager balM *evmORMMocks.BalanceMonitor - bptxmORM *bulletprooftxmanagerMocks.ORM + txmORM *txmgrMocks.ORM } // gqlTestFramework is a framework wrapper containing the objects needed to run @@ -107,7 +107,7 @@ func setupFramework(t *testing.T) *gqlTestFramework { ethClient: &evmORMMocks.Client{}, eIMgr: &webhookmocks.ExternalInitiatorManager{}, balM: &evmORMMocks.BalanceMonitor{}, - bptxmORM: &bulletprooftxmanagerMocks.ORM{}, + txmORM: &txmgrMocks.ORM{}, } // Assert expectations for any mocks that we set up @@ -135,7 +135,7 @@ func setupFramework(t *testing.T) *gqlTestFramework { m.ethClient, m.eIMgr, m.balM, - m.bptxmORM, + m.txmORM, ) }) diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index 3e742f77a0f..0c3405b1cae 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -2,9 +2,9 @@ package resolver import ( "github.com/graph-gophers/graphql-go" - "github.com/smartcontractkit/chainlink/core/utils/stringutils" "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/utils/stringutils" "github.com/smartcontractkit/chainlink/core/web/gqlscalar" ) @@ -53,7 +53,7 @@ func (r *SpecResolver) ToOCRSpec() (*OCRSpecResolver, bool) { return nil, false } - return &OCRSpecResolver{spec: *r.j.OffchainreportingOracleSpec}, true + return &OCRSpecResolver{spec: *r.j.OCROracleSpec}, true } func (r *SpecResolver) ToOCR2Spec() (*OCR2SpecResolver, bool) { @@ -61,7 +61,7 @@ func (r *SpecResolver) ToOCR2Spec() (*OCR2SpecResolver, bool) { return nil, false } - return &OCR2SpecResolver{spec: *r.j.Offchainreporting2OracleSpec}, true + return &OCR2SpecResolver{spec: *r.j.OCR2OracleSpec}, true } func (r *SpecResolver) ToVRFSpec() (*VRFSpecResolver, bool) { @@ -296,7 +296,7 @@ func (r *KeeperSpecResolver) FromAddress() *string { } type OCRSpecResolver struct { - spec job.OffchainReportingOracleSpec + spec job.OCROracleSpec } // BlockchainTimeout resolves the spec's blockchain timeout. @@ -478,7 +478,7 @@ func (r *OCRSpecResolver) TransmitterAddress() *string { } type OCR2SpecResolver struct { - spec job.OffchainReporting2OracleSpec + spec job.OCR2OracleSpec } // BlockchainTimeout resolves the spec's blockchain timeout. @@ -525,15 +525,6 @@ func (r *OCR2SpecResolver) CreatedAt() graphql.Time { return graphql.Time{Time: r.spec.CreatedAt} } -// JuelsPerFeeCoinSource resolves the spec's juels per fee coin source -func (r *OCR2SpecResolver) JuelsPerFeeCoinSource() *string { - if r.spec.JuelsPerFeeCoinPipeline == "" { - return nil - } - - return &r.spec.JuelsPerFeeCoinPipeline -} - // KeyBundleID resolves the spec's key bundle id. func (r *OCR2SpecResolver) OcrKeyBundleID() *string { if !r.spec.OCRKeyBundleID.Valid { @@ -573,7 +564,17 @@ func (r *OCR2SpecResolver) RelayConfig() gqlscalar.Map { return gqlscalar.Map(r.spec.RelayConfig) } -// TransmitterAddress resolves the spec's transmitter id +// PluginType resolves the spec's plugin type +func (r *OCR2SpecResolver) PluginType() string { + return string(r.spec.PluginType) +} + +// PluginConfig resolves the spec's plugin config +func (r *OCR2SpecResolver) PluginConfig() gqlscalar.Map { + return gqlscalar.Map(r.spec.PluginConfig) +} + +// TransmitterID resolves the spec's transmitter id func (r *OCR2SpecResolver) TransmitterID() *string { if !r.spec.TransmitterID.Valid { return nil @@ -618,14 +619,17 @@ func (r *VRFSpecResolver) EVMChainID() *string { return &chainID } -// FromAddress resolves the spec's from address. -func (r *VRFSpecResolver) FromAddress() *string { - if r.spec.FromAddress == nil { +// FromAddresses resolves the spec's from addresses. +func (r *VRFSpecResolver) FromAddresses() *[]string { + if len(r.spec.FromAddresses) == 0 { return nil } - addr := r.spec.FromAddress.String() - return &addr + var addresses []string + for _, a := range r.spec.FromAddresses { + addresses = append(addresses, a.Address().String()) + } + return &addresses } // PollPeriod resolves the spec's poll period. diff --git a/core/web/resolver/spec_test.go b/core/web/resolver/spec_test.go index 41f9820e922..1a1ad659c99 100644 --- a/core/web/resolver/spec_test.go +++ b/core/web/resolver/spec_test.go @@ -4,8 +4,6 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/services/relay/types" - "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" "github.com/stretchr/testify/require" @@ -15,6 +13,7 @@ import ( clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/keystore/keys/ethkey" + "github.com/smartcontractkit/chainlink/core/services/relay/types" "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -372,7 +371,7 @@ func TestResolver_OCRSpec(t *testing.T) { f.App.On("JobORM").Return(f.Mocks.jobORM) f.Mocks.jobORM.On("FindJobTx", id).Return(job.Job{ Type: job.OffchainReporting, - OffchainreportingOracleSpec: &job.OffchainReportingOracleSpec{ + OCROracleSpec: &job.OCROracleSpec{ BlockchainTimeout: models.Interval(1 * time.Minute), BlockchainTimeoutEnv: false, ContractAddress: contractAddress, @@ -488,6 +487,9 @@ func TestResolver_OCR2Spec(t *testing.T) { relayConfig := map[string]interface{}{ "chainID": 1337, } + pluginConfig := map[string]interface{}{ + "juelsPerFeeCoinSource": 100000000, + } require.NoError(t, err) testCases := []GQLTestCase{ @@ -498,19 +500,20 @@ func TestResolver_OCR2Spec(t *testing.T) { f.App.On("JobORM").Return(f.Mocks.jobORM) f.Mocks.jobORM.On("FindJobTx", id).Return(job.Job{ Type: job.OffchainReporting2, - Offchainreporting2OracleSpec: &job.OffchainReporting2OracleSpec{ + OCR2OracleSpec: &job.OCR2OracleSpec{ BlockchainTimeout: models.Interval(1 * time.Minute), ContractID: contractAddress.String(), ContractConfigConfirmations: 1, ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), CreatedAt: f.Timestamp(), - JuelsPerFeeCoinPipeline: "100000000", OCRKeyBundleID: null.StringFrom(keyBundleID.String()), MonitoringEndpoint: null.StringFrom("https://monitor.endpoint"), P2PBootstrapPeers: pq.StringArray{"12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw@localhost:5001"}, Relay: types.EVM, RelayConfig: relayConfig, TransmitterID: null.StringFrom(transmitterAddress.String()), + PluginType: job.Median, + PluginConfig: pluginConfig, }, }, nil) }, @@ -526,13 +529,14 @@ func TestResolver_OCR2Spec(t *testing.T) { contractConfigConfirmations contractConfigTrackerPollInterval createdAt - juelsPerFeeCoinSource ocrKeyBundleID monitoringEndpoint p2pBootstrapPeers relay relayConfig transmitterID + pluginType + pluginConfig } } } @@ -549,7 +553,6 @@ func TestResolver_OCR2Spec(t *testing.T) { "contractConfigConfirmations": 1, "contractConfigTrackerPollInterval": "1m0s", "createdAt": "2021-01-01T00:00:00Z", - "juelsPerFeeCoinSource": "100000000", "ocrKeyBundleID": "f5bf259689b26f1374efb3c9a9868796953a0f814bb2d39b968d0e61b58620a5", "monitoringEndpoint": "https://monitor.endpoint", "p2pBootstrapPeers": ["12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw@localhost:5001"], @@ -557,7 +560,11 @@ func TestResolver_OCR2Spec(t *testing.T) { "relayConfig": { "chainID": 1337 }, - "transmitterID": "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42" + "transmitterID": "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42", + "pluginType": "median", + "pluginConfig": { + "juelsPerFeeCoinSource": 100000000 + } } } } @@ -575,7 +582,10 @@ func TestResolver_VRFSpec(t *testing.T) { coordinatorAddress, err := ethkey.NewEIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C") require.NoError(t, err) - fromAddress, err := ethkey.NewEIP55Address("0x3cCad4715152693fE3BC4460591e3D3Fbd071b42") + fromAddress1, err := ethkey.NewEIP55Address("0x3cCad4715152693fE3BC4460591e3D3Fbd071b42") + require.NoError(t, err) + + fromAddress2, err := ethkey.NewEIP55Address("0x2301958F1BFbC9A068C2aC9c6166Bf483b95864C") require.NoError(t, err) pubKey, err := secp256k1.NewPublicKeyFromHex("0x9dc09a0f898f3b5e8047204e7ce7e44b587920932f08431e29c9bf6923b8450a01") @@ -594,7 +604,7 @@ func TestResolver_VRFSpec(t *testing.T) { CoordinatorAddress: coordinatorAddress, CreatedAt: f.Timestamp(), EVMChainID: utils.NewBigI(42), - FromAddress: &fromAddress, + FromAddresses: []ethkey.EIP55Address{fromAddress1, fromAddress2}, PollPeriod: 1 * time.Minute, PublicKey: pubKey, RequestedConfsDelay: 10, @@ -612,7 +622,7 @@ func TestResolver_VRFSpec(t *testing.T) { coordinatorAddress createdAt evmChainID - fromAddress + fromAddresses minIncomingConfirmations pollPeriod publicKey @@ -632,7 +642,7 @@ func TestResolver_VRFSpec(t *testing.T) { "coordinatorAddress": "0x613a38AC1659769640aaE063C651F48E0250454C", "createdAt": "2021-01-01T00:00:00Z", "evmChainID": "42", - "fromAddress": "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42", + "fromAddresses": ["0x3cCad4715152693fE3BC4460591e3D3Fbd071b42", "0x2301958F1BFbC9A068C2aC9c6166Bf483b95864C"], "minIncomingConfirmations": 1, "pollPeriod": "1m0s", "publicKey": "0x9dc09a0f898f3b5e8047204e7ce7e44b587920932f08431e29c9bf6923b8450a01", diff --git a/core/web/resolver/vrf.go b/core/web/resolver/vrf.go index 208403a0ff4..d00280a8385 100644 --- a/core/web/resolver/vrf.go +++ b/core/web/resolver/vrf.go @@ -77,7 +77,7 @@ func NewVRFKeyPayloadResolver(key vrfkey.KeyV2, err error) *VRFKeyPayloadResolve if err != nil { e = NotFoundErrorUnionType{err: err, message: err.Error(), isExpectedErrorFn: func(err error) bool { - return errors.Cause(err) == keystore.ErrMissingVRFKey + return errors.Is(errors.Cause(err), keystore.ErrMissingVRFKey) }} } @@ -141,7 +141,7 @@ func NewDeleteVRFKeyPayloadResolver(key vrfkey.KeyV2, err error) *DeleteVRFKeyPa if err != nil { e = NotFoundErrorUnionType{err: err, message: err.Error(), isExpectedErrorFn: func(err error) bool { - return errors.Cause(err) == keystore.ErrMissingVRFKey + return errors.Is(errors.Cause(err), keystore.ErrMissingVRFKey) }} } diff --git a/core/web/router.go b/core/web/router.go index 0712d1a91f4..ef3f5c99d14 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -25,6 +25,7 @@ import ( "github.com/gin-gonic/gin" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" + "github.com/pkg/errors" "github.com/ulule/limiter" mgin "github.com/ulule/limiter/drivers/middleware/gin" "github.com/ulule/limiter/drivers/store/memory" @@ -251,8 +252,11 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { authv2.PATCH("/bridge_types/:BridgeName", bt.Update) authv2.DELETE("/bridge_types/:BridgeName", bt.Destroy) - ts := TransfersController{app} - authv2.POST("/transfers", ts.Create) + ets := EVMTransfersController{app} + authv2.POST("/transfers", ets.Create) + authv2.POST("/transfers/evm", ets.Create) + tts := TerraTransfersController{app} + authv2.POST("/transfers/terra", tts.Create) cc := ConfigController{app} authv2.GET("/config", cc.Show) @@ -266,8 +270,11 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { tas := TxAttemptsController{app} authv2.GET("/tx_attempts", paginatedRequest(tas.Index)) + authv2.GET("/tx_attempts/evm", paginatedRequest(tas.Index)) txs := TransactionsController{app} + authv2.GET("/transactions/evm", paginatedRequest(txs.Index)) + authv2.GET("/transactions/evm/:TxHash", txs.Show) authv2.GET("/transactions", paginatedRequest(txs.Index)) authv2.GET("/transactions/:TxHash", txs.Show) @@ -377,12 +384,20 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { authv2.POST("/nodes/evm", enc.Create) authv2.DELETE("/nodes/evm/:ID", enc.Delete) + efc := EVMForwardersController{app} + authv2.GET("/nodes/evm/forwarders", paginatedRequest(efc.Index)) + authv2.POST("/nodes/evm/forwarders", efc.Create) + authv2.DELETE("/nodes/evm/forwarders/:fwdID", efc.Delete) + tnc := TerraNodesController{app} authv2.GET("/nodes/terra", paginatedRequest(tnc.Index)) authv2.GET("/chains/terra/:ID/nodes", paginatedRequest(tnc.Index)) authv2.POST("/nodes/terra", tnc.Create) authv2.DELETE("/nodes/terra/:ID", tnc.Delete) + build_info := BuildInfoController{app} + authv2.GET("/build_info", build_info.Show) + // Debug routes accessible via authentication metricRoutes(authv2) } @@ -455,7 +470,7 @@ func guiAssetRoutes(engine *gin.Engine, config config.GeneralConfig, lggr logger // Render the React index page for any other unknown requests file, err := assetFs.Open("index.html") if err != nil { - if err == fs.ErrNotExist { + if errors.Is(err, fs.ErrNotExist) { c.AbortWithStatus(http.StatusNotFound) } else { lggr.Errorf("failed to open static file '%s': %+v", path, err) diff --git a/core/web/router_test.go b/core/web/router_test.go index a1648ba4275..5ac25a231d9 100644 --- a/core/web/router_test.go +++ b/core/web/router_test.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/bridges" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" @@ -17,7 +18,7 @@ import ( func TestTokenAuthRequired_NoCredentials(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -31,7 +32,7 @@ func TestTokenAuthRequired_NoCredentials(t *testing.T) { func TestTokenAuthRequired_SessionCredentials(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -46,7 +47,7 @@ func TestTokenAuthRequired_SessionCredentials(t *testing.T) { func TestTokenAuthRequired_TokenCredentials(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -78,7 +79,7 @@ func TestTokenAuthRequired_TokenCredentials(t *testing.T) { func TestTokenAuthRequired_BadTokenCredentials(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -110,7 +111,7 @@ func TestTokenAuthRequired_BadTokenCredentials(t *testing.T) { func TestSessions_RateLimited(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -138,7 +139,7 @@ func TestSessions_RateLimited(t *testing.T) { func TestRouter_LargePOSTBody(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) @@ -157,7 +158,7 @@ func TestRouter_LargePOSTBody(t *testing.T) { func TestRouter_GinHelmetHeaders(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) router := web.Router(app, nil) ts := httptest.NewServer(router) diff --git a/core/web/schema/type/feeds_manager.graphql b/core/web/schema/type/feeds_manager.graphql index 2aa0cff05a7..7a9b4d85cce 100644 --- a/core/web/schema/type/feeds_manager.graphql +++ b/core/web/schema/type/feeds_manager.graphql @@ -1,6 +1,7 @@ enum JobType { FLUX_MONITOR OCR + OCR2 } type FeedsManager { diff --git a/core/web/schema/type/job_proposal.graphql b/core/web/schema/type/job_proposal.graphql index b58a3303962..1c798e69313 100644 --- a/core/web/schema/type/job_proposal.graphql +++ b/core/web/schema/type/job_proposal.graphql @@ -10,6 +10,7 @@ type JobProposal { status: JobProposalStatus! remoteUUID: String! externalJobID: String + jobID: String feedsManager: FeedsManager! multiAddrs: [String!]! pendingUpdate: Boolean! diff --git a/core/web/schema/type/node.graphql b/core/web/schema/type/node.graphql index bba55968af1..6343e261768 100644 --- a/core/web/schema/type/node.graphql +++ b/core/web/schema/type/node.graphql @@ -6,6 +6,7 @@ type Node { chain: Chain! createdAt: Time! updatedAt: Time! + state: String! } union NodePayload = Node | NotFoundError diff --git a/core/web/schema/type/spec.graphql b/core/web/schema/type/spec.graphql index 362284b34df..e63e0dc67fc 100644 --- a/core/web/schema/type/spec.graphql +++ b/core/web/schema/type/spec.graphql @@ -80,20 +80,21 @@ type OCR2Spec { contractConfigConfirmations: Int contractConfigTrackerPollInterval: String createdAt: Time! - juelsPerFeeCoinSource: String ocrKeyBundleID: String monitoringEndpoint: String p2pBootstrapPeers: [String!] relay: String! relayConfig: Map! transmitterID: String + pluginType: String! + pluginConfig: Map! } type VRFSpec { coordinatorAddress: String! createdAt: Time! evmChainID: String - fromAddress: String + fromAddresses: [String!] minIncomingConfirmations: Int! minIncomingConfirmationsEnv: Boolean! pollPeriod: String! diff --git a/core/web/sessions_controller_test.go b/core/web/sessions_controller_test.go index f75b8a7f42e..fae723ec04e 100644 --- a/core/web/sessions_controller_test.go +++ b/core/web/sessions_controller_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/pg" "github.com/smartcontractkit/chainlink/core/sessions" "github.com/smartcontractkit/chainlink/core/web" @@ -22,7 +23,7 @@ func TestSessionsController_Create(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) config := app.GetConfig() client := http.Client{} @@ -82,7 +83,7 @@ func TestSessionsController_Create_ReapSessions(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) staleSession := cltest.NewSession() staleSession.LastUsed = time.Now().Add(-cltest.MustParseDuration(t, "241h")) @@ -112,7 +113,7 @@ func TestSessionsController_Destroy(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) correctSession := sessions.NewSession() q := pg.NewQ(app.GetSqlxDB(), app.GetLogger(), app.GetConfig()) @@ -155,7 +156,7 @@ func TestSessionsController_Destroy_ReapSessions(t *testing.T) { client := http.Client{} app := cltest.NewApplicationEVMDisabled(t) q := pg.NewQ(app.GetSqlxDB(), app.GetLogger(), app.GetConfig()) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) correctSession := sessions.NewSession() mustInsertSession(t, q, &correctSession) diff --git a/core/web/solana_keys_controller_test.go b/core/web/solana_keys_controller_test.go index 430b0d01e3a..97205c47cef 100644 --- a/core/web/solana_keys_controller_test.go +++ b/core/web/solana_keys_controller_test.go @@ -6,10 +6,12 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +40,7 @@ func TestSolanaKeysController_Create_HappyPath(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() keyStore := app.GetKeyStore() @@ -93,7 +95,7 @@ func setupSolanaKeysControllerTests(t *testing.T) (cltest.HTTPClientCleaner, key t.Helper() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) app.KeyStore.OCR().Add(cltest.DefaultOCRKey) app.KeyStore.Solana().Add(cltest.DefaultSolanaKey) diff --git a/core/web/terra_chains_controller.go b/core/web/terra_chains_controller.go index e1de94b6e90..19f1eaf8c5a 100644 --- a/core/web/terra_chains_controller.go +++ b/core/web/terra_chains_controller.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" + "github.com/smartcontractkit/chainlink/core/services/chainlink" "github.com/smartcontractkit/chainlink/core/web/presenters" ) @@ -19,7 +20,12 @@ type TerraChainsController struct { // Index lists Terra chains. func (cc *TerraChainsController) Index(c *gin.Context, size, page, offset int) { - chains, count, err := cc.App.TerraORM().Chains(offset, size) + terraChains := cc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + chains, count, err := terraChains.ORM().Chains(offset, size) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) @@ -42,7 +48,12 @@ type CreateTerraChainRequest struct { // Show gets a Terra chain by chain id. func (cc *TerraChainsController) Show(c *gin.Context) { - chain, err := cc.App.TerraORM().Chain(c.Param("ID")) + terraChains := cc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + chain, err := terraChains.ORM().Chain(c.Param("ID")) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) return @@ -53,6 +64,12 @@ func (cc *TerraChainsController) Show(c *gin.Context) { // Create adds a new Terra chain. func (cc *TerraChainsController) Create(c *gin.Context) { + terraChains := cc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + request := &CreateTerraChainRequest{} if err := c.ShouldBindJSON(&request); err != nil { @@ -60,7 +77,7 @@ func (cc *TerraChainsController) Create(c *gin.Context) { return } - chain, err := cc.App.GetChains().Terra.Add(request.ID, request.Config) + chain, err := terraChains.Add(c.Request.Context(), request.ID, request.Config) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) @@ -78,13 +95,19 @@ type UpdateTerraChainRequest struct { // Update configures an existing Terra chain. func (cc *TerraChainsController) Update(c *gin.Context) { + terraChains := cc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + var request UpdateTerraChainRequest if err := c.ShouldBindJSON(&request); err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) return } - chain, err := cc.App.GetChains().Terra.Configure(c.Param("ID"), request.Enabled, request.Config) + chain, err := terraChains.Configure(c.Request.Context(), c.Param("ID"), request.Enabled, request.Config) if errors.Is(err, sql.ErrNoRows) { jsonAPIError(c, http.StatusNotFound, err) @@ -99,7 +122,12 @@ func (cc *TerraChainsController) Update(c *gin.Context) { // Delete removes a Terra chain. func (cc *TerraChainsController) Delete(c *gin.Context) { - err := cc.App.GetChains().Terra.Remove(c.Param("ID")) + terraChains := cc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + err := terraChains.Remove(c.Param("ID")) if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) diff --git a/core/web/terra_chains_controller_test.go b/core/web/terra_chains_controller_test.go index 394e21a898d..bc8121bbb41 100644 --- a/core/web/terra_chains_controller_test.go +++ b/core/web/terra_chains_controller_test.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink-terra/pkg/terra/db" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/internal/testutils/terratest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/web" @@ -307,7 +308,7 @@ func Test_TerraChainsController_Delete(t *testing.T) { } terratest.MustInsertChain(t, controller.app.GetSqlxDB(), &chain) - _, countBefore, err := controller.app.TerraORM().Chains(0, 10) + _, countBefore, err := controller.app.Chains.Terra.ORM().Chains(0, 10) require.NoError(t, err) require.Equal(t, 1, countBefore) @@ -316,7 +317,7 @@ func Test_TerraChainsController_Delete(t *testing.T) { t.Cleanup(cleanup) require.Equal(t, http.StatusInternalServerError, resp.StatusCode) - _, countAfter, err := controller.app.TerraORM().Chains(0, 10) + _, countAfter, err := controller.app.Chains.Terra.ORM().Chains(0, 10) require.NoError(t, err) require.Equal(t, 1, countAfter) }) @@ -328,11 +329,11 @@ func Test_TerraChainsController_Delete(t *testing.T) { t.Cleanup(cleanup) require.Equal(t, http.StatusNoContent, resp.StatusCode) - _, countAfter, err := controller.app.TerraORM().Chains(0, 10) + _, countAfter, err := controller.app.Chains.Terra.ORM().Chains(0, 10) require.NoError(t, err) require.Equal(t, 0, countAfter) - _, err = controller.app.TerraORM().Chain(chain.ID) + _, err = controller.app.Chains.Terra.ORM().Chain(chain.ID) assert.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) @@ -345,10 +346,12 @@ type TestTerraChainsController struct { } func setupTerraChainsControllerTest(t *testing.T) *TestTerraChainsController { - // Using this instead of `NewApplicationTerraDisabled` since we need the chain set to be loaded in the app - // for the sake of the API endpoints to work properly - app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + cfg := cltest.NewTestGeneralConfig(t) + cfg.Overrides.TerraEnabled = null.BoolFrom(true) + cfg.Overrides.EVMEnabled = null.BoolFrom(false) + cfg.Overrides.EVMRPCEnabled = null.BoolFrom(false) + app := cltest.NewApplicationWithConfig(t, cfg) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() diff --git a/core/web/terra_keys_controller_test.go b/core/web/terra_keys_controller_test.go index 72d2359c6db..e482aeaa07c 100644 --- a/core/web/terra_keys_controller_test.go +++ b/core/web/terra_keys_controller_test.go @@ -6,10 +6,12 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/services/keystore" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" "github.com/smartcontractkit/chainlink/core/web/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +40,7 @@ func TestTerraKeysController_Create_HappyPath(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() keyStore := app.GetKeyStore() @@ -93,7 +95,7 @@ func setupTerraKeysControllerTests(t *testing.T) (cltest.HTTPClientCleaner, keys t.Helper() app := cltest.NewApplication(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) app.KeyStore.Terra().Add(cltest.DefaultTerraKey) client := app.NewHTTPClient() diff --git a/core/web/terra_nodes_controller.go b/core/web/terra_nodes_controller.go index db402266dc6..75322c277e7 100644 --- a/core/web/terra_nodes_controller.go +++ b/core/web/terra_nodes_controller.go @@ -16,6 +16,9 @@ import ( "github.com/smartcontractkit/chainlink/core/web/presenters" ) +// ErrTerraNotEnabled is returned when TERRA_ENABLED is not true. +var ErrTerraNotEnabled = errors.New("Terra is disabled. Set TERRA_ENABLED=true to enable.") + // TerraNodesController manages Terra nodes. type TerraNodesController struct { App chainlink.Application @@ -23,6 +26,13 @@ type TerraNodesController struct { // Index lists Terra nodes, and optionally filters by chain id. func (nc *TerraNodesController) Index(c *gin.Context, size, page, offset int) { + terraChains := nc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + orm := terraChains.ORM() + id := c.Param("ID") var nodes []db.Node @@ -31,10 +41,10 @@ func (nc *TerraNodesController) Index(c *gin.Context, size, page, offset int) { if id == "" { // fetch all nodes - nodes, count, err = nc.App.TerraORM().Nodes(offset, size) + nodes, count, err = orm.Nodes(offset, size) } else { - nodes, count, err = nc.App.TerraORM().NodesForChain(id, offset, size) + nodes, count, err = orm.NodesForChain(id, offset, size) } var resources []presenters.TerraNodeResource @@ -47,6 +57,13 @@ func (nc *TerraNodesController) Index(c *gin.Context, size, page, offset int) { // Create adds a new Terra node. func (nc *TerraNodesController) Create(c *gin.Context) { + terraChains := nc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + orm := terraChains.ORM() + var request types.NewNode if err := c.ShouldBindJSON(&request); err != nil { @@ -55,7 +72,7 @@ func (nc *TerraNodesController) Create(c *gin.Context) { } // Ensure chain exists. - if _, err := nc.App.TerraORM().Chain(request.TerraChainID); err != nil { + if _, err := orm.Chain(request.TerraChainID); err != nil { if errors.Is(err, sql.ErrNoRows) { jsonAPIError(c, http.StatusBadRequest, fmt.Errorf("Terra chain %s must be added first", request.TerraChainID)) return @@ -64,7 +81,7 @@ func (nc *TerraNodesController) Create(c *gin.Context) { return } - node, err := nc.App.TerraORM().CreateNode(request) + node, err := orm.CreateNode(request) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) @@ -76,13 +93,20 @@ func (nc *TerraNodesController) Create(c *gin.Context) { // Delete removes a Terra node. func (nc *TerraNodesController) Delete(c *gin.Context) { + terraChains := nc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + orm := terraChains.ORM() + id, err := strconv.ParseInt(c.Param("ID"), 10, 32) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) return } - err = nc.App.TerraORM().DeleteNode(int32(id)) + err = orm.DeleteNode(int32(id)) if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) diff --git a/core/web/terra_transfer_controller.go b/core/web/terra_transfer_controller.go new file mode 100644 index 00000000000..593ad2a029b --- /dev/null +++ b/core/web/terra_transfer_controller.go @@ -0,0 +1,139 @@ +package web + +import ( + "net/http" + + sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink-terra/pkg/terra/client" + + "github.com/smartcontractkit/chainlink/core/chains/terra" + "github.com/smartcontractkit/chainlink/core/chains/terra/denom" + "github.com/smartcontractkit/chainlink/core/services/chainlink" + terramodels "github.com/smartcontractkit/chainlink/core/store/models/terra" + "github.com/smartcontractkit/chainlink/core/web/presenters" +) + +// maxGasUsedTransfer is an upper bound on how much gas we expect a MsgSend for a single coin to use. +const maxGasUsedTransfer = 100_000 + +// TerraTransfersController can send LINK tokens to another address +type TerraTransfersController struct { + App chainlink.Application +} + +// Create sends Luna and other native coins from the Chainlink's account to a specified address. +func (tc *TerraTransfersController) Create(c *gin.Context) { + terraChains := tc.App.GetChains().Terra + if terraChains == nil { + jsonAPIError(c, http.StatusBadRequest, ErrTerraNotEnabled) + return + } + + var tr terramodels.SendRequest + if err := c.ShouldBindJSON(&tr); err != nil { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + if tr.TerraChainID == "" { + jsonAPIError(c, http.StatusBadRequest, errors.New("missing terraChainID")) + return + } + chain, err := terraChains.Chain(c.Request.Context(), tr.TerraChainID) + switch err { + case terra.ErrChainIDInvalid, terra.ErrChainIDEmpty: + jsonAPIError(c, http.StatusBadRequest, err) + return + case nil: + break + default: + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + if tr.FromAddress.Empty() { + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("withdrawal source address is missing: %v", tr.FromAddress)) + return + } + + coin, err := denom.ConvertToULuna(sdk.NewDecCoinFromDec("luna", tr.Amount)) + if err != nil { + jsonAPIError(c, http.StatusBadRequest, errors.Errorf("unable to convert to uluna: %v", err)) + return + } else if !coin.Amount.IsPositive() { + jsonAPIError(c, http.StatusBadRequest, errors.Errorf("amount must be greater than zero: %s", coin.Amount)) + return + } + + txm := chain.TxManager() + + if !tr.AllowHigherAmounts { + var reader client.Reader + reader, err = chain.Reader("") + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("chain unreachable: %v", err)) + return + } + gasPrice, err := txm.GasPrice() + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("gas price unavailable: %v", err)) + return + } + + err = terraValidateBalance(reader, gasPrice, tr.FromAddress, coin) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("failed to validate balance: %v", err)) + return + } + } + + sendMsg := bank.NewMsgSend(tr.FromAddress, tr.DestinationAddress, sdk.Coins{coin}) + msgID, err := txm.Enqueue("", sendMsg) + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("transaction failed: %v", err)) + return + } + resource := presenters.NewTerraMsgResource(msgID, tr.TerraChainID, "") + msgs, err := txm.GetMsgs(msgID) + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("failed to get message %d: %v", msgID, err)) + return + } + if len(msgs) != 1 { + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("failed to get message %d: %v", msgID, err)) + return + } + msg := msgs[0] + resource.TxHash = msg.TxHash + resource.State = string(msg.State) + + jsonAPIResponse(c, resource, "terra_msg") +} + +// terraValidateBalance validates that fromAddr's balance can cover coin, including fees at gasPrice. +// Note: This is currently limited to uluna only, for both gasPrice and coin. +func terraValidateBalance(reader client.Reader, gasPrice sdk.DecCoin, fromAddr sdk.AccAddress, coin sdk.Coin) error { + const denom = "uluna" + if gasPrice.Denom != denom { + return errors.Errorf("unsupported gas price denom: %s", gasPrice.Denom) + } + if coin.Denom != denom { + return errors.Errorf("unsupported coin denom: %s", gasPrice.Denom) + } + + balance, err := reader.Balance(fromAddr, denom) + if err != nil { + return err + } + + fee := gasPrice.Amount.MulInt64(maxGasUsedTransfer).RoundInt() + need := coin.Amount.Add(fee) + + if balance.Amount.LT(need) { + return errors.Errorf("balance %q is too low for this transaction to be executed: need %s total, including %s fee", balance, need, fee) + } + return nil +} diff --git a/core/web/user_controller_test.go b/core/web/user_controller_test.go index be29e68e048..f6856da7839 100644 --- a/core/web/user_controller_test.go +++ b/core/web/user_controller_test.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/testutils" "github.com/smartcontractkit/chainlink/core/sessions" "github.com/stretchr/testify/assert" @@ -17,7 +18,7 @@ import ( func TestUserController_UpdatePassword(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() @@ -70,7 +71,7 @@ func TestUserController_NewAPIToken(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() req, err := json.Marshal(sessions.ChangeAuthTokenRequest{ @@ -92,7 +93,7 @@ func TestUserController_NewAPIToken_unauthorized(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() req, err := json.Marshal(sessions.ChangeAuthTokenRequest{ @@ -108,7 +109,7 @@ func TestUserController_DeleteAPIKey(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() req, err := json.Marshal(sessions.ChangeAuthTokenRequest{ @@ -125,7 +126,7 @@ func TestUserController_DeleteAPIKey_unauthorized(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - require.NoError(t, app.Start()) + require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient() req, err := json.Marshal(sessions.ChangeAuthTokenRequest{ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e5528946d14..20a09acb928 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ... +## [1.3.0] - 2022-04-18 + +### Added + +- Added disk rotating logs. Chainlink will now always log to disk at debug level. The default output directory for debug logs is Chainlink's root directory (ROOT_DIR) but can be configured by setting LOG_FILE_DIR. This makes it easier for node operators to report useful debugging information to Chainlink's team, since all the debug logs are conveniently located in one directory. Regular logging to STDOUT still works as before and respects the LOG_LEVEL env var. If you want to log in disk at a particular level, you can pipe STDOUT to disk. This automatic debug-logs-to-disk feature is enabled by default, and will remain enabled as long as the `LOG_FILE_MAX_SIZE` ENV var is set to a value greater than zero. The amount of disk space required for this feature to work can be calculated with the following formula: `LOG_FILE_MAX_SIZE` * (`LOG_FILE_MAX_BACKUPS` + 1). If your disk doesn't have enough disk space, the logging will pause and the application will log Errors until space is available again. New environment variables related to this feature: + - `LOG_FILE_MAX_SIZE` (default: 5120mb) - this env var allows you to override the log file's max size (in megabytes) before file rotation. + - `LOG_FILE_MAX_AGE` (default: 0) - if `LOG_FILE_MAX_SIZE` is set, this env var allows you to override the log file's max age (in days) before file rotation. Keeping this config with the default value means not to remove old log files. + - `LOG_FILE_MAX_BACKUPS` (default: 1) - if `LOG_FILE_MAX_SIZE` is set, this env var allows you to override the max amount of old log files to retain. Keeping this config with the default value means to retain 1 old log file at most (though `LOG_FILE_MAX_AGE` may still cause them to get deleted). If this is set to 0, the node will retain all old log files instead. +- Added support for the `force` flag on `chainlink blocks replay`. If set to true, already consumed logs that would otherwise be skipped will be rebroadcasted. +- Added version compatibility check when using CLI to login to a remote node. flag `bypass-version-check` skips this check. +- Interrim solution to set multiple nodes/chains from ENV. This gives the ability to specify multiple RPCs that the Chainlink node will constantly monitor for health and sync status, detecting dead nodes and out of sync nodes, with automatic failover. This is a temporary stand-in until configuration is overhauled and will be removed in future in favor of a config file. Set as such: `EVM_NODES='{...}'` where the var is a JSON array containing the node specifications. This is not compatible with using any other way to specify node via env (e.g. `ETH_URL`, `ETH_SECONDARY_URL`, `ETH_CHAIN_ID` etc). **WARNING**: Setting this environment variable will COMPLETELY ERASE your `evm_nodes` table on every boot and repopulate from the given data, nullifying any runtime modifications. Make sure to carefully read the [EVM performance configuration guide](https://chainlink.notion.site/EVM-performance-configuration-handbook-a36b9f84dcac4569ba68772aa0c1368c) for best practices here. + +For example: + +```bash +export EVM_NODES=' +[ + { + "name": "primary_1", + "evmChainId": "137", + "wsUrl": "wss://endpoint-1.example.com/ws", + "httpUrl": "http://endpoint-1.example.com/", + "sendOnly": false + }, + { + "name": "primary_2", + "evmChainId": "137", + "wsUrl": "ws://endpoint-2.example.com/ws", + "httpUrl": "http://endpoint-2.example.com/", + "sendOnly": false + }, + { + "name": "primary_3", + "evmChainId": "137", + "wsUrl": "wss://endpoint-3.example.com/ws", + "httpUrl": "http://endpoint-3.example.com/", + "sendOnly": false + }, + { + "name": "sendonly_1", + "evmChainId": "137", + "httpUrl": "http://endpoint-4.example.com/", + "sendOnly": true + }, + { + "name": "sendonly_2", + "evmChainId": "137", + "httpUrl": "http://endpoint-5.example.com/", + "sendOnly": true + } +] +' +``` + +### Changed + +- Changed default locking mode to "dual". Bugs in lease locking have been ironed out and this paves the way to making "lease" the default in future. It is recommended to set `DATABASE_LOCKING_MODE=lease`, default is set to "dual" only for backwards compatibility. +- EIP-1559 is now enabled by default on mainnet. To disable (go back to legacy mode) set `EVM_EIP1559_DYNAMIC_FEES=false`. The default settings should work well, but if you wish to tune your gas controls, see the [documentation](https://docs.chain.link/docs/configuration-variables/#evm-gas-controls). + +Note that EIP-1559 can be manually enabled on other chains by setting `EVM_EIP1559_DYNAMIC_FEES=true` but we only support it for official Ethereum mainnet and testnets. It is _not_ recommended to enable this setting on Polygon since during our testing process we found that the EIP-1559 fee market appears to be broken on all Polygon chains and EIP-1559 transactions are actually less likely to get included than legacy transactions. + +See issue: https://github.com/maticnetwork/bor/issues/347 + +### Removed + +- `LOG_TO_DISK` ENV var. ## [1.2.1] - 2022-03-17 @@ -29,7 +95,7 @@ New ENV vars: - `ADVISORY_LOCK_CHECK_INTERVAL` (default: 1s) - when advisory locking mode is enabled, this controls how often Chainlink checks to make sure it still holds the advisory lock. It is recommended to leave this at the default. - `ADVISORY_LOCK_ID` (default: 1027321974924625846) - when advisory locking mode is enabled, the application advisory lock ID can be changed using this env var. All instances of Chainlink that might run on a particular database must share the same advisory lock ID. It is recommended to leave this at the default. -- `LOG_FILE_DIR` (default: chainlink root directory) - if `LOG_TO_DISK` is enabled, this env var allows you to override the output directory for logging. +- `LOG_FILE_DIR` (default: chainlink root directory) - if `LOG_FILE_MAX_SIZE` is set, this env var allows you to override the output directory for logging. - `SHUTDOWN_GRACE_PERIOD` (default: 5s) - when node is shutting down gracefully and exceeded this grace period, it terminates immediately (trying to close DB connection) to avoid being SIGKILLed. - `SOLANA_ENABLED` (default: false) - set to true to enable Solana support - `TERRA_ENABLED` (default: false) - set to true to enable Terra support @@ -37,7 +103,11 @@ New ENV vars: - `TELEMETRY_INGRESS_BUFFER_SIZE` (default: 100) - the number of telemetry messages to buffer before dropping new ones - `TELEMETRY_INGRESS_MAX_BATCH_SIZE` (default: 50) - the maximum number of messages to batch into one telemetry request - `TELEMETRY_INGRESS_SEND_INTERVAL` (default: 500ms) - the cadence on which batched telemetry is sent to the ingress server +- `TELEMETRY_INGRESS_SEND_TIMEOUT` (default: 10s) - the max duration to wait for the request to complete when sending batch telemetry - `TELEMETRY_INGRESS_USE_BATCH_SEND` (default: true) - toggles sending telemetry using the batch client to the ingress server +- `NODE_NO_NEW_HEADS_THRESHOLD` (default: 3m) - RPC node will be marked out-of-sync if it does not receive a new block for this length of time. Set to 0 to disable head monitoring for liveness checking, +- `NODE_POLL_FAILURE_THRESHOLD` (default: 5) - number of consecutive failed polls before an RPC node is marked dead. Set to 0 to disable poll liveness checking. +- `NODE_POLL_INTERVAL` (default: 10s) - how often to poll. Set to 0 to disable all polling. #### Bootstrap job @@ -55,6 +125,12 @@ contractID = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" chainID = 4 ``` +#### EVM node hot failover and liveness checking + +Chainlink now supports hot failover and liveness checking for EVM nodes. This completely supercedes and replaces the Fiews failover proxy and should remove the need for any kind of failover proxy between Chainlink and its RPC nodes. + +In order to use this feature, you'll need to set multiple primary RPC nodes. + ### Removed - `deleteuser` CLI command. diff --git a/go.mod b/go.mod index af42a4e8e7c..d171b2a8f70 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/smartcontractkit/chainlink go 1.17 require ( - github.com/Depado/ginprom v1.7.2 + github.com/Depado/ginprom v1.7.3 github.com/Masterminds/semver/v3 v3.1.1 github.com/btcsuite/btcd v0.22.0-beta github.com/cosmos/cosmos-sdk v0.44.5 @@ -16,22 +16,22 @@ require ( github.com/fatih/color v1.13.0 github.com/fxamacker/cbor/v2 v2.4.0 github.com/gagliardetto/solana-go v1.0.4 - github.com/getsentry/sentry-go v0.11.0 + github.com/getsentry/sentry-go v0.12.0 github.com/gin-contrib/cors v1.3.1 github.com/gin-contrib/expvar v0.0.0-20181230111036-f23b556cc79f github.com/gin-contrib/size v0.0.0-20190528085907-355431950c57 github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0 - github.com/gin-gonic/gin v1.7.4 + github.com/gin-gonic/gin v1.7.7 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 github.com/google/uuid v1.3.0 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.1 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.5.0 github.com/graph-gophers/dataloader v5.0.0+incompatible github.com/graph-gophers/graphql-go v1.3.0 github.com/hdevalence/ed25519consensus v0.0.0-20210430192048-0962ce16b305 github.com/jackc/pgconn v1.11.0 - github.com/jackc/pgx/v4 v4.13.0 + github.com/jackc/pgx/v4 v4.15.0 github.com/jpillora/backoff v1.0.0 github.com/kylelemons/godebug v1.1.0 github.com/lib/pq v1.10.4 @@ -44,25 +44,22 @@ require ( github.com/multiformats/go-multiaddr v0.3.3 github.com/okex/exchain-ethereum-compatible v1.1.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/ginkgo/v2 v2.1.2 github.com/onsi/gomega v1.18.1 github.com/pelletier/go-toml v1.9.4 github.com/pkg/errors v0.9.1 - github.com/pressly/goose/v3 v3.4.1 + github.com/pressly/goose/v3 v3.5.3 github.com/prometheus/client_golang v1.12.1 github.com/robfig/cron/v3 v3.0.1 - github.com/rs/zerolog v1.26.1 github.com/satori/go.uuid v1.2.0 github.com/scylladb/go-reflectx v1.0.1 + github.com/shirou/gopsutil/v3 v3.22.2 github.com/shopspring/decimal v1.3.1 - github.com/smartcontractkit/chainlink-solana v0.2.12-0.20220217150457-281a05e940f1 - github.com/smartcontractkit/chainlink-terra v0.0.8-0.20220222145923-77cd6baf7b57 - github.com/smartcontractkit/helmenv v1.0.36 - github.com/smartcontractkit/integrations-framework v1.0.50 + github.com/smartcontractkit/chainlink-solana v0.2.18-0.20220315140817-b4df0b6bc414 + github.com/smartcontractkit/chainlink-terra v0.1.4-0.20220315114020-a15962b0ed9b github.com/smartcontractkit/libocr v0.0.0-20220217180537-449836e6cfec github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb github.com/smartcontractkit/terra.go v1.0.3-0.20220108002221-62b39252ee16 - github.com/smartcontractkit/wsrpc v0.3.5 + github.com/smartcontractkit/wsrpc v0.3.10-0.20220317191700-8c8ecdcaed4a github.com/spf13/cobra v1.3.0 github.com/spf13/viper v1.10.1 github.com/stretchr/testify v1.7.0 @@ -77,56 +74,37 @@ require ( go.dedis.ch/fixbuf v1.0.3 go.dedis.ch/kyber/v3 v3.0.13 go.uber.org/atomic v1.9.0 - go.uber.org/multierr v1.7.0 - go.uber.org/zap v1.19.1 - golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e + go.uber.org/multierr v1.8.0 + go.uber.org/zap v1.21.0 + golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 - golang.org/x/tools v0.1.7 + golang.org/x/tools v0.1.9 gonum.org/v1/gonum v0.9.3 google.golang.org/protobuf v1.27.1 gopkg.in/guregu/null.v4 v4.0.0 ) require ( - cloud.google.com/go v0.99.0 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/keyring v1.1.6 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.20 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v0.4.1 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/CosmWasm/wasmvm v0.16.3 // indirect github.com/DataDog/zstd v1.4.5 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.3.10 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blendle/zapdriver v1.3.1 // indirect github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect - github.com/cavaliercoder/grab v2.0.0+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 // indirect github.com/confio/ics23/go v0.6.6 // indirect github.com/containerd/containerd v1.5.9 // indirect @@ -137,7 +115,6 @@ require ( github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect - github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect @@ -147,61 +124,41 @@ require ( github.com/dgraph-io/ristretto v0.0.3 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/docker/cli v20.10.11+incompatible // indirect github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.4 // indirect - github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect - github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/fvbommel/sortorder v1.0.1 // indirect github.com/gagliardetto/binary v0.5.2 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect - github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-errors/errors v1.4.1 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.2.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect - github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/gogo/protobuf v1.3.3 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/google/btree v1.0.1 // indirect github.com/google/certificate-transparency-go v1.0.21 // indirect - github.com/google/go-cmp v0.5.6 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect - github.com/gosuri/uitable v0.0.4 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect @@ -215,9 +172,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.0 // indirect - github.com/huandu/xstrings v1.3.2 // indirect github.com/huin/goupnp v1.0.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/go-cid v0.0.7 // indirect github.com/ipfs/go-datastore v0.4.5 // indirect @@ -228,22 +183,18 @@ require ( github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgproto3/v2 v2.2.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.8.1 // indirect + github.com/jackc/pgtype v1.10.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.4 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/koron/go-ssdp v0.0.2 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/libp2p/go-addr-util v0.0.2 // indirect @@ -282,10 +233,8 @@ require ( github.com/libp2p/go-tcp-transport v0.2.1 // indirect github.com/libp2p/go-ws-transport v0.4.0 // indirect github.com/libp2p/go-yamux/v2 v2.0.0 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/magiconair/properties v1.8.5 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -293,18 +242,10 @@ require ( github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/sha256-simd v0.1.1 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect @@ -319,9 +260,9 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect @@ -332,11 +273,9 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rjeczalik/notify v0.9.2 // indirect github.com/rs/cors v1.8.0 // indirect - github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857 // indirect - github.com/russross/blackfriday v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect - github.com/shirou/gopsutil v3.21.10+incompatible // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -361,48 +300,23 @@ require ( github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xlab/treeprint v1.1.0 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/zondax/hid v0.9.0 // indirect go.dedis.ch/protobuf v1.0.11 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect - go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 // indirect go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect google.golang.org/grpc v1.43.0 // indirect - gopkg.in/gorp.v1 v1.7.2 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - helm.sh/helm/v3 v3.8.0 // indirect - k8s.io/api v0.23.3 // indirect - k8s.io/apiextensions-apiserver v0.23.1 // indirect - k8s.io/apimachinery v0.23.3 // indirect - k8s.io/apiserver v0.23.1 // indirect - k8s.io/cli-runtime v0.23.3 // indirect - k8s.io/client-go v0.23.3 // indirect - k8s.io/component-base v0.23.3 // indirect - k8s.io/klog/v2 v2.30.0 // indirect - k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect - k8s.io/kubectl v0.23.3 // indirect - k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect - oras.land/oras-go v1.1.0 // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect - sigs.k8s.io/kustomize/api v0.10.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) // To fix CVE: c16fb56d-9de6-4065-9fca-d2b4cfb13020 diff --git a/go.sum b/go.sum index 593376a2621..7711fd57245 100644 --- a/go.sum +++ b/go.sum @@ -34,7 +34,6 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= -cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -81,44 +80,32 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= -github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= -github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= @@ -126,15 +113,13 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CosmWasm/wasmvm v0.16.0/go.mod h1:Id107qllDJyJjVQQsKMOy2YYF98sqPJ2t+jX1QES40A= github.com/CosmWasm/wasmvm v0.16.3 h1:hUf33EHRmyyvKMhwVl7nMaAOY0vYJVB4bhU+HPfHfBM= github.com/CosmWasm/wasmvm v0.16.3/go.mod h1:Id107qllDJyJjVQQsKMOy2YYF98sqPJ2t+jX1QES40A= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -142,8 +127,8 @@ github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Depado/ginprom v1.2.1-0.20200115153638-53bbba851bd8/go.mod h1:VHRucFf/9saDXsYg6uzQ8Oo8gUwngtWec9ZJ00H+ZCc= -github.com/Depado/ginprom v1.7.2 h1:uwarHKylLo2CVvx45ajxY/GnCsqPzez5tFT4pwrZWIs= -github.com/Depado/ginprom v1.7.2/go.mod h1:cMlQwg/daHJdgGlaDHBlzse+7CZxkVgcqZqvDcIKg6c= +github.com/Depado/ginprom v1.7.3 h1:3e/MujyuVbm73D0ReFsMKSW6P7PLXjMcZVeukY2C150= +github.com/Depado/ginprom v1.7.3/go.mod h1:VnTr/tCjWPNw5h/DF4lc3z9fqZ/aLNgJ0PUp3Vvxo/Y= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -151,21 +136,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= -github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -184,34 +156,23 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3 github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim v0.9.1 h1:VfDCj+QnY19ktX5TsH22JHcjaZ05RWQiwDbOyEg5ziM= -github.com/Microsoft/hcsshim v0.9.1/go.mod h1:Y/0uV2jUab5kBI7SQgl62at0AVX7uaruzADAVmxm3eM= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= @@ -244,7 +205,6 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -267,16 +227,12 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= @@ -294,7 +250,6 @@ github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAm github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -322,8 +277,6 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= @@ -351,24 +304,18 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= -github.com/cavaliercoder/grab v2.0.0+incompatible h1:wZHbBQx56+Yxjx2TCGDcenhh3cJn7cCLMfkEPmySTSE= -github.com/cavaliercoder/grab v2.0.0+incompatible/go.mod h1:tTBkfNqSBfuMmMBFaO2phgyhdYhiZQ/+iXCZDzcDsMI= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -378,8 +325,6 @@ github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= @@ -414,9 +359,6 @@ github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= @@ -443,8 +385,6 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.2 h1:mZBclaSgNDfPWtfhj2xJY28LZ9nYIgzB0pwSURPl6JM= -github.com/containerd/cgroups v1.0.2/go.mod h1:qpbpJ1jmlqsR9f2IyaLPsdkCdnt0rbDVqIDlhuu5tRY= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= @@ -463,8 +403,6 @@ github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7 github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.9 h1:rs6Xg1gtIxaeyG+Smsb/0xaSDu1VgFhOCKBXxMxbsF4= github.com/containerd/containerd v1.5.9/go.mod h1:fvQqCfadDGga5HZyn3j4+dx56qj2I9YwBrlSdalvJYQ= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -476,7 +414,7 @@ github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.0/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= -github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -497,7 +435,6 @@ github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJ github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= @@ -570,12 +507,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKY github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -591,7 +525,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= @@ -606,8 +539,7 @@ github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRk github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= @@ -631,39 +563,27 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= -github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= -github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -688,7 +608,6 @@ github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -718,13 +637,6 @@ github.com/ethereum/go-ethereum v1.10.11/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQ github.com/ethereum/go-ethereum v1.10.16 h1:3oPrumn0bCW/idjcxMn5YYVCdK7VzJYIvwGZUGLEaoc= github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -732,8 +644,6 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -769,8 +679,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= @@ -791,12 +699,9 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSG github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8= -github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo= +github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= @@ -815,8 +720,9 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.6.0/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= -github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= @@ -824,7 +730,6 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclK github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.1 h1:IvVlgbzSsaUNudsw5dcXSzF3EWyXTi5XrAdngnuhRyg= -github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -849,33 +754,18 @@ github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNV github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= @@ -902,18 +792,9 @@ github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= -github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= -github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= -github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= -github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= -github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= -github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -924,8 +805,6 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= @@ -935,13 +814,13 @@ github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -989,17 +868,13 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= -github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -1014,9 +889,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -1024,7 +899,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -1052,7 +926,6 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1069,9 +942,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= @@ -1095,10 +965,9 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= @@ -1107,8 +976,6 @@ github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1 github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.1/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -1199,9 +1066,6 @@ github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= @@ -1216,7 +1080,6 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/improbable-eng/grpc-web v0.14.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= @@ -1298,7 +1161,6 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= @@ -1315,25 +1177,27 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= -github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= +github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= -github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= +github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -1367,7 +1231,6 @@ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= @@ -1375,12 +1238,8 @@ github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v0.0.0-20170918002102-8eab2debe79d/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -1411,8 +1270,6 @@ github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9/go.mod h1:Od972xHfMJo github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= -github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= @@ -1420,8 +1277,6 @@ github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= -github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= @@ -1448,7 +1303,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= -github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -1464,13 +1318,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a h1:dHCfT5W7gghzPtfsW488uPmEOm85wewI+ypUwibyTdU= @@ -1485,7 +1335,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= @@ -1712,16 +1561,13 @@ github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.0.0 h1:vSGhAy5u6iHBq11ZDcyHH4Blcf9xlBhT4WQDoOE90LU= github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1729,23 +1575,13 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk= @@ -1761,6 +1597,7 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= @@ -1779,7 +1616,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1788,21 +1624,18 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= -github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= +github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= @@ -1832,19 +1665,12 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -1858,23 +1684,13 @@ github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1882,10 +1698,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= @@ -1955,7 +1769,6 @@ github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= @@ -2011,8 +1824,7 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.2 h1:QUvZA5LiZ5EMDS0dVTQbjOvYLFs3wzcztqFU/mfR70c= -github.com/onsi/ginkgo/v2 v2.1.2/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -2072,7 +1884,7 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= -github.com/ory/dockertest/v3 v3.8.0/go.mod h1:9zPATATlWQru+ynXP+DytBQrsXV7Tmlx7K86H6fQaDo= +github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -2094,14 +1906,11 @@ github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhEC github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -2111,6 +1920,7 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -2123,9 +1933,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pressly/goose/v3 v3.4.1 h1:1qZcFO+1K9gKnOHoHGDC2wtZu4BmVSnECyy48YzSLx0= -github.com/pressly/goose/v3 v3.4.1/go.mod h1:dlZhFJpEBA/+BF8IYJyxUxA0fQvJKmsJHi0liv52EiQ= +github.com/pressly/goose/v3 v3.5.3 h1:lIQIIXVbdO2RuQtJBS1e7MZjKEk0demVWt6i0YPiOrg= +github.com/pressly/goose/v3 v3.5.3/go.mod h1:IL4NNMdXx9O6hHpGbNB5l1hkVe/Avoz4gBDE5g7rQNg= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -2137,7 +1949,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= -github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -2162,9 +1973,7 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= @@ -2213,7 +2022,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -2221,18 +2029,11 @@ github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= -github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857 h1:nI2V0EI64bEYpbyOmwYfk0DYu26j0k4LhC7YS4tKkhA= -github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -2249,7 +2050,6 @@ github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F7 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ= github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -2258,12 +2058,12 @@ github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KR github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v3.21.10+incompatible h1:AL2kpVykjkqeN+MFe1WcwSBVUjGjvdU8/ubvCuXAjrU= -github.com/shirou/gopsutil v3.21.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= +github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= @@ -2302,14 +2102,10 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartcontractkit/chainlink v0.8.10-0.20200825114219-81dd2fc95bac/go.mod h1:j7qIYHGCN4QqMXdO8g8A9dmUT5vKFmkxPSbjAIfrfNU= github.com/smartcontractkit/chainlink v0.9.5-0.20201207211610-6c7fee37d5b7/go.mod h1:kmdLJbVZRCnBLiL6gG+U+1+0ofT3bB48DOF8tjQvcoI= -github.com/smartcontractkit/chainlink-solana v0.2.12-0.20220217150457-281a05e940f1 h1:nrFnL9McbNpy+fmtPZixWGgqQoZnHyqiu5BDUuwiR40= -github.com/smartcontractkit/chainlink-solana v0.2.12-0.20220217150457-281a05e940f1/go.mod h1:ptiyWN0cpK6ZsVmGyAqjJxG6fs5tgg0YYZL33sit1CA= -github.com/smartcontractkit/chainlink-terra v0.0.8-0.20220222145923-77cd6baf7b57 h1:UIMd7V5wej/TZvQ/zifOtHpMnTtA959swHzDJwxNiWE= -github.com/smartcontractkit/chainlink-terra v0.0.8-0.20220222145923-77cd6baf7b57/go.mod h1:u+yg56Xh9AZs6it6MvrxV5qv2gEIbOOmrlDgnM5Xmac= -github.com/smartcontractkit/helmenv v1.0.36 h1:RSgEyvWstAuH5BCssPeSeDrxywvAFHP8sqw2Ft55VaQ= -github.com/smartcontractkit/helmenv v1.0.36/go.mod h1:pkScfFRZM9UhMAqeG+Bfxyy7YjLWh8pY6vmNGdTpKJs= -github.com/smartcontractkit/integrations-framework v1.0.50 h1:r9f0pRWGOT4CcPUx6XAf+LRON8SDbda3zbf2kR1O8WQ= -github.com/smartcontractkit/integrations-framework v1.0.50/go.mod h1:IZyYezzgpwa1Ir3iZMjAnJfwg+pJB5kyExBiwZqQe9c= +github.com/smartcontractkit/chainlink-solana v0.2.18-0.20220315140817-b4df0b6bc414 h1:2gjlzPo/FrxcSM28R2IxI4oCoNaG3cy3OZdByUHsy/E= +github.com/smartcontractkit/chainlink-solana v0.2.18-0.20220315140817-b4df0b6bc414/go.mod h1:6aPUF3+paoUKqxiCKCDAnPkWjv5PtZyPY4+HCEjGbiI= +github.com/smartcontractkit/chainlink-terra v0.1.4-0.20220315114020-a15962b0ed9b h1:t6aU0/re3Oy1lUwc0Jxnqjs8hx24wiyFh82yLSm+Bj0= +github.com/smartcontractkit/chainlink-terra v0.1.4-0.20220315114020-a15962b0ed9b/go.mod h1:jUCC6lW9Y8iWOupdw72u1ds22fCpM/qGqmeN5PQERoE= github.com/smartcontractkit/libocr v0.0.0-20201203233047-5d9b24f0cbb5/go.mod h1:bfdSuLnBWCkafDvPGsQ1V6nrXhg046gh227MKi4zkpc= github.com/smartcontractkit/libocr v0.0.0-20220217180537-449836e6cfec h1:vkTYAihTA8qQ30LKd0272NWLzOCWWCw78qk0LtIej0A= github.com/smartcontractkit/libocr v0.0.0-20220217180537-449836e6cfec/go.mod h1:nq3crM3wVqnyMlM/4ZydTuJ/WyCapAsOt7P94oRgSPg= @@ -2317,15 +2113,14 @@ github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb h1:OMaBUb4 github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb/go.mod h1:HNUu4cJekUdsJbwRBCiOybtkPJEfGRELQPe2tkoDEyk= github.com/smartcontractkit/terra.go v1.0.3-0.20220108002221-62b39252ee16 h1:k+E0RKzVSG1QpxXakNUtcGUhq4ZMe0MAJ5Awg/l9oSc= github.com/smartcontractkit/terra.go v1.0.3-0.20220108002221-62b39252ee16/go.mod h1:48ia8cZcgAEKED8yTNp09Dtb4VWlLrOSSPC7U89W3n4= -github.com/smartcontractkit/wsrpc v0.3.5 h1:i4SZGK+wKGvb6GQI9tD/mcYoRgfc5xKai3UpePVkcLY= -github.com/smartcontractkit/wsrpc v0.3.5/go.mod h1:9ciG9g+0Hb9FgqUmt7MHz3EhTbLjbEoftcq8McErZN0= +github.com/smartcontractkit/wsrpc v0.3.10-0.20220317191700-8c8ecdcaed4a h1:CQA5SbRZ/X7PAWRjVBht01a8TbvoW+kr5iEYxQ3QIOE= +github.com/smartcontractkit/wsrpc v0.3.10-0.20220317191700-8c8ecdcaed4a/go.mod h1:6ObiCKal7kzRjszPzNuc058lBGIOmvW9Hc80E4aqtDU= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -2349,7 +2144,6 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= @@ -2382,7 +2176,6 @@ github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48/go.mod h1:RZL github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -2484,7 +2277,6 @@ github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2bi github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= @@ -2546,18 +2338,11 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtaci/kcp-go v5.4.5+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= @@ -2571,15 +2356,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -2608,9 +2391,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -2624,21 +2404,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 h1:oBcONsksxvpeodDrLjiMDaKHXKAVVfAydhe/792CE/o= -go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -2648,14 +2414,16 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -2667,9 +2435,9 @@ go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2700,14 +2468,12 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -2728,9 +2494,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2785,8 +2551,9 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20170324220409-6c2325251549/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2846,7 +2613,6 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -2855,23 +2621,22 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2891,7 +2656,6 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2955,7 +2719,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3010,7 +2773,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3018,7 +2781,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3026,7 +2788,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3038,11 +2799,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3050,16 +2809,17 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3085,7 +2845,6 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -3107,7 +2866,6 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -3140,18 +2898,15 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -3159,7 +2914,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -3176,9 +2930,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -3245,7 +2998,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -3286,14 +3038,11 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -3411,15 +3160,12 @@ gopkg.in/go-playground/validator.v8 v8.18.1/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/R gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= -gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/guregu/null.v2 v2.1.2 h1:YOuepWdYqGnrenzPyMi+ybCjeDzjdazynbwsXXOk4i8= gopkg.in/guregu/null.v2 v2.1.2/go.mod h1:XORrx8tyS5ZDcyUboCIxQtta/Aujk/6pfWrn9Xe33mU= gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -3433,6 +3179,7 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mN gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= @@ -3464,7 +3211,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= @@ -3473,8 +3219,6 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -helm.sh/helm/v3 v3.8.0 h1:vlQQDDQkrH4NECOFbGcwjjKyHL5Sa3xNLjMxXm7fMVo= -helm.sh/helm/v3 v3.8.0/go.mod h1:0nYPSuvuj8TTJDLRSAfbzGGbazPZsayaDpP8s9FfZT8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -3488,73 +3232,31 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= -k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= -k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= -k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.23.1 h1:vWGf8LcV9Pk/z5rdLmCiBDqE21ccbe930dzrtVMhw9g= -k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= -k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0= -k8s.io/cli-runtime v0.23.3 h1:aJiediw+uUbxkfO6BNulcAMTUoU9Om43g3R7rIkYqcw= -k8s.io/cli-runtime v0.23.3/go.mod h1:yA00O5pDqnjkBh8fkuugBbfIfjB1nOpz+aYLotbnOfc= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= -k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= -k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= -k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= -k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= -k8s.io/component-base v0.23.3 h1:q+epprVdylgecijVGVdf4MbizEL2feW4ssd7cdo6LVY= -k8s.io/component-base v0.23.3/go.mod h1:1Smc4C60rWG7d3HjSYpIwEbySQ3YWg0uzH5a2AtaTLg= -k8s.io/component-helpers v0.23.1/go.mod h1:ZK24U+2oXnBPcas2KolLigVVN9g5zOzaHLkHiQMFGr0= -k8s.io/component-helpers v0.23.3/go.mod h1:SH+W/WPTaTenbWyDEeY7iytAQiMh45aqKxkvlqQ57cg= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kubectl v0.23.1/go.mod h1:Ui7dJKdUludF8yWAOSN7JZEkOuYixX5yF6E6NjoukKE= -k8s.io/kubectl v0.23.3 h1:gJsF7cahkWDPYlNvYKK+OrBZLAJUBzCym+Zsi+dfi1E= -k8s.io/kubectl v0.23.3/go.mod h1:VBeeXNgLhSabu4/k0O7Q0YujgnA3+CLTUE0RcmF73yY= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.23.1/go.mod h1:qXvsM1KANrc+ZZeFwj6Phvf0NLiC+d3RwcsLcdGc+xs= -k8s.io/metrics v0.23.3/go.mod h1:Ut8TvkbsO4oMVeUzaTArvPrcw9QRFLs2XNzUlORjdYE= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= -k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= @@ -3567,8 +3269,11 @@ modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.17 h1:sWWFJxgj2whIJ5P/rzgHalMgpcIhkVSRgiLV0XA7p6Y= modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.22 h1:BzShpwCAP7TWzFppM4k2t03RhXhgYqaibROWkrWq7lE= +modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= @@ -3598,8 +3303,23 @@ modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7I modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= -modernc.org/ccgo/v3 v3.12.65 h1:k2m2owVfoAQ55AnED+M7w7WnEkt0+Z+XY0qpdGOh3gI= -modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= +modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU= +modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko= +modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA= +modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4= +modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0= +modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0= +modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8= +modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I= +modernc.org/ccgo/v3 v3.15.13 h1:hqlCzNJTXLrhS70y1PqWckrF9x1btSQRC7JFuQcBg5c= +modernc.org/ccgo/v3 v3.15.13/go.mod h1:QHtvdpeODlXjdK3tsbpyK+7U9JV4PQsrPGIbtmc0KfY= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/ccorpus v1.11.4/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= @@ -3630,9 +3350,20 @@ modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= -modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= -modernc.org/libc v1.11.71 h1:iF84u92whsBbZG6puONw4En33xL6jGSKnTMoUql1t+w= modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= +modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ= +modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c= +modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI= +modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ= +modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk= +modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34= +modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ= +modernc.org/libc v1.14.5 h1:DAHvwGoVRDZs5iJXnX9RJrgXSsorupCWmJ2ac964Owk= +modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -3643,43 +3374,26 @@ modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.14.1 h1:jthfQCbWKfbK/lvZSjFEpBk0QzIBN6pQbFdDqBMR490= -modernc.org/sqlite v1.14.1/go.mod h1:04Lqa+3PuAEUhAPAPWeDMljT4UYA31nb2DHTFG47L1g= +modernc.org/sqlite v1.14.6 h1:Jt5P3k80EtDBWaq1beAxnWW+5MdHXbZITujnRS7+zWg= +modernc.org/sqlite v1.14.6/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= +modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= +modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -oras.land/oras-go v1.1.0 h1:tfWM1RT7PzUwWphqHU6ptPU3ZhwVnSw/9nEGf519rYg= -oras.land/oras-go v1.1.0/go.mod h1:1A7vR/0KknT2UkJVWh+xMi95I/AhK8ZrxrnUSmXN0bQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= -sigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0= -sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= -sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= -sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= -sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY= -sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/integration-tests/go.mod b/integration-tests/go.mod new file mode 100644 index 00000000000..b276c0cc9ec --- /dev/null +++ b/integration-tests/go.mod @@ -0,0 +1,181 @@ +module github.com/smartcontractkit/chainlink/integration-tests + +go 1.17 + +require ( + github.com/ethereum/go-ethereum v1.10.16 + github.com/onsi/ginkgo/v2 v2.1.3 + github.com/onsi/gomega v1.18.1 + github.com/rs/zerolog v1.26.1 + github.com/satori/go.uuid v1.2.0 + github.com/smartcontractkit/helmenv v1.0.38 + github.com/smartcontractkit/integrations-framework v1.0.50 +) + +require ( + cloud.google.com/go v0.99.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.20 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/BurntSushi/toml v0.4.1 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/squirrel v1.5.2 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/cavaliercoder/grab v2.0.0+incompatible // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect + github.com/containerd/containerd v1.5.9 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect + github.com/docker/cli v20.10.11+incompatible // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v20.10.12+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/camelcase v1.0.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/fvbommel/sortorder v1.0.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-errors/errors v1.4.1 // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmoiron/sqlx v1.3.4 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.4 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rjeczalik/notify v0.9.2 // indirect + github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/shirou/gopsutil v3.21.10+incompatible // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/smartcontractkit/libocr v0.0.0-20220121130134-5d2b1d5f424b // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.3.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.10.1 // indirect + github.com/stretchr/testify v1.7.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.9 // indirect + github.com/tklauser/numcpus v0.3.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect + golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect + google.golang.org/grpc v1.43.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/gorp.v1 v1.7.2 // indirect + gopkg.in/guregu/null.v4 v4.0.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + helm.sh/helm/v3 v3.8.0 // indirect + k8s.io/api v0.23.4 // indirect + k8s.io/apiextensions-apiserver v0.23.1 // indirect + k8s.io/apimachinery v0.23.4 // indirect + k8s.io/apiserver v0.23.1 // indirect + k8s.io/cli-runtime v0.23.4 // indirect + k8s.io/client-go v0.23.4 // indirect + k8s.io/component-base v0.23.4 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + k8s.io/kubectl v0.23.4 // indirect + k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect + oras.land/oras-go v1.1.0 // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/kustomize/api v0.10.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/integration-tests/go.sum b/integration-tests/go.sum new file mode 100644 index 00000000000..c0fc6708d5f --- /dev/null +++ b/integration-tests/go.sum @@ -0,0 +1,2898 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= +github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= +github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Depado/ginprom v1.2.1-0.20200115153638-53bbba851bd8/go.mod h1:VHRucFf/9saDXsYg6uzQ8Oo8gUwngtWec9ZJ00H+ZCc= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= +github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.1 h1:VfDCj+QnY19ktX5TsH22JHcjaZ05RWQiwDbOyEg5ziM= +github.com/Microsoft/hcsshim v0.9.1/go.mod h1:Y/0uV2jUab5kBI7SQgl62at0AVX7uaruzADAVmxm3eM= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= +github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= +github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= +github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/aristanetworks/goarista v0.0.0-20190204200901-2166578f3448/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/aristanetworks/goarista v0.0.0-20191023202215-f096da5361bb/go.mod h1:Z4RTxGAuYhPzcq8+EdRM+R8M48Ssle2TsWtwRKa+vns= +github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boj/redistore v0.0.0-20160128113310-fc113767cd6b/go.mod h1:5r9chGCb4uUhBCGMDDCYfyHU/awSRoBeG53Zaj1crhU= +github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cavaliercoder/grab v2.0.0+incompatible h1:wZHbBQx56+Yxjx2TCGDcenhh3cJn7cCLMfkEPmySTSE= +github.com/cavaliercoder/grab v2.0.0+incompatible/go.mod h1:tTBkfNqSBfuMmMBFaO2phgyhdYhiZQ/+iXCZDzcDsMI= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= +github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.2 h1:mZBclaSgNDfPWtfhj2xJY28LZ9nYIgzB0pwSURPl6JM= +github.com/containerd/cgroups v1.0.2/go.mod h1:qpbpJ1jmlqsR9f2IyaLPsdkCdnt0rbDVqIDlhuu5tRY= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.5.9 h1:rs6Xg1gtIxaeyG+Smsb/0xaSDu1VgFhOCKBXxMxbsF4= +github.com/containerd/containerd v1.5.9/go.mod h1:fvQqCfadDGga5HZyn3j4+dx56qj2I9YwBrlSdalvJYQ= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= +github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= +github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/ethereum/go-ethereum v1.9.18/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg= +github.com/ethereum/go-ethereum v1.9.24/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM= +github.com/ethereum/go-ethereum v1.10.11/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= +github.com/ethereum/go-ethereum v1.10.16 h1:3oPrumn0bCW/idjcxMn5YYVCdK7VzJYIvwGZUGLEaoc= +github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= +github.com/gin-contrib/expvar v0.0.0-20181230111036-f23b556cc79f/go.mod h1:lKdjvm96uBFqhKVjrNmDm66m199oRiOxJAThyr6rdW8= +github.com/gin-contrib/size v0.0.0-20190528085907-355431950c57/go.mod h1:tnsW+BI6lwWXXNN+IJNJGRPjs10RYHJFCnbUOO1EPWc= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gin-gonic/gin v1.6.0/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.1 h1:IvVlgbzSsaUNudsw5dcXSzF3EWyXTi5XrAdngnuhRyg= +github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= +github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20170307001533-c9c7427a2a70/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= +github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/guregu/null v3.5.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= +github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= +github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= +github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= +github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= +github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= +github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= +github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= +github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipns v0.0.2/go.mod h1:WChil4e0/m9cIINWLxZe1Jtf77oz5L05rO2ei/uKJ5U= +github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= +github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= +github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= +github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= +github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= +github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/gorm v1.9.11-0.20190912141731-0c98e7d712e2/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v0.0.0-20170918002102-8eab2debe79d/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= +github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= +github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= +github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= +github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= +github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= +github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= +github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= +github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= +github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= +github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= +github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= +github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El8cTaefiM= +github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8= +github.com/libp2p/go-libp2p v0.10.2/go.mod h1:BYckt6lmS/oA1SlRETSPWSUulCQKiZuTVsymVMc//HQ= +github.com/libp2p/go-libp2p v0.12.0/go.mod h1:FpHZrfC1q7nA8jitvdjKBDF31hguaC676g/nT9PgQM0= +github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20201026210036-4f868c957324/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= +github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= +github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= +github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= +github.com/libp2p/go-libp2p-autonat v0.2.3/go.mod h1:2U6bNWCNsAG9LEbwccBDQbjzQ8Krdjge1jLTE9rdoMM= +github.com/libp2p/go-libp2p-autonat v0.3.1/go.mod h1:0OzOi1/cVc7UcxfOddemYD5vzEqi4fwRbnZcJGLi68U= +github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= +github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= +github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= +github.com/libp2p/go-libp2p-blankhost v0.1.6/go.mod h1:jONCAJqEP+Z8T6EQviGL4JsQcLx1LgTGtVqFNY8EMfQ= +github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= +github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= +github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= +github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= +github.com/libp2p/go-libp2p-circuit v0.2.3/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= +github.com/libp2p/go-libp2p-circuit v0.3.1/go.mod h1:8RMIlivu1+RxhebipJwFDA45DasLx+kkrp4IlJj53F4= +github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= +github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= +github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= +github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= +github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= +github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= +github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= +github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= +github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= +github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= +github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= +github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= +github.com/libp2p/go-libp2p-discovery v0.4.0/go.mod h1:bZ0aJSrFc/eX2llP0ryhb1kpgkPyTo23SJ5b7UQCMh4= +github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-kad-dht v0.8.3/go.mod h1:HnYYy8taJWESkqiESd1ngb9XX/XGGsMA5G0Vj2HoSh4= +github.com/libp2p/go-libp2p-kad-dht v0.11.1/go.mod h1:5ojtR2acDPqh/jXf5orWy8YGb8bHQDS+qeDcoscL/PI= +github.com/libp2p/go-libp2p-kbucket v0.4.2/go.mod h1:7sCeZx2GkNK1S6lQnGUW5JYZCFPnXzAZCCBBS70lytY= +github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= +github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= +github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= +github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= +github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= +github.com/libp2p/go-libp2p-mplex v0.2.4/go.mod h1:mI7iOezdWFOisvUwaYd3IDrJ4oVmgoXK8H331ui39CE= +github.com/libp2p/go-libp2p-mplex v0.3.0/go.mod h1:l9QWxRbbb5/hQMECEb908GbS9Sm2UAR2KFZKUJEynEs= +github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= +github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= +github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= +github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= +github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= +github.com/libp2p/go-libp2p-noise v0.1.2/go.mod h1:9B10b7ueo7TIxZHHcjcDCo5Hd6kfKT2m77by82SFRfE= +github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= +github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= +github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= +github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= +github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= +github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= +github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M= +github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= +github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= +github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= +github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= +github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= +github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= +github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= +github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= +github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= +github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= +github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYRNYp1xs4S2FBWKA= +github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= +github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.3.1/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= +github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= +github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= +github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= +github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= +github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= +github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= +github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= +github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= +github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= +github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= +github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= +github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= +github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= +github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= +github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= +github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= +github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= +github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= +github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.4/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= +github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= +github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= +github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= +github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= +github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= +github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= +github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= +github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= +github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= +github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= +github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= +github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= +github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= +github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= +github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= +github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= +github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= +github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= +github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= +github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= +github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= +github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= +github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= +github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= +github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= +github.com/multiformats/go-multistream v0.1.2/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= +github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= +github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181120120127-aeab699e26f4/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= +github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= +github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= +github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857 h1:nI2V0EI64bEYpbyOmwYfk0DYu26j0k4LhC7YS4tKkhA= +github.com/rubenv/sql-migrate v0.0.0-20211023115951-9f02b1e13857/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.10+incompatible h1:AL2kpVykjkqeN+MFe1WcwSBVUjGjvdU8/ubvCuXAjrU= +github.com/shirou/gopsutil v3.21.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartcontractkit/chainlink v0.8.10-0.20200825114219-81dd2fc95bac/go.mod h1:j7qIYHGCN4QqMXdO8g8A9dmUT5vKFmkxPSbjAIfrfNU= +github.com/smartcontractkit/chainlink v0.9.5-0.20201207211610-6c7fee37d5b7/go.mod h1:kmdLJbVZRCnBLiL6gG+U+1+0ofT3bB48DOF8tjQvcoI= +github.com/smartcontractkit/helmenv v1.0.38 h1:9BcFn2fEVm5pjm3LqI8ANLiL6i5yF82JH6vCaNqvS88= +github.com/smartcontractkit/helmenv v1.0.38/go.mod h1:Co8Gvpy0In6lrMxHQJOS0oZ8XzFs25Sfg5+cpd2kcKI= +github.com/smartcontractkit/integrations-framework v1.0.50 h1:r9f0pRWGOT4CcPUx6XAf+LRON8SDbda3zbf2kR1O8WQ= +github.com/smartcontractkit/integrations-framework v1.0.50/go.mod h1:IZyYezzgpwa1Ir3iZMjAnJfwg+pJB5kyExBiwZqQe9c= +github.com/smartcontractkit/libocr v0.0.0-20201203233047-5d9b24f0cbb5/go.mod h1:bfdSuLnBWCkafDvPGsQ1V6nrXhg046gh227MKi4zkpc= +github.com/smartcontractkit/libocr v0.0.0-20220121130134-5d2b1d5f424b h1:9xEvwk6fHqL9Fp/u8bLd7kbEQKrDSzwuDsH1ptHjE9g= +github.com/smartcontractkit/libocr v0.0.0-20220121130134-5d2b1d5f424b/go.mod h1:nq3crM3wVqnyMlM/4ZydTuJ/WyCapAsOt7P94oRgSPg= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 h1:ju5UTwk5Odtm4trrY+4Ca4RMj5OyXbmVeDAVad2T0Jw= +github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= +github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= +github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulule/limiter v0.0.0-20190417201358-7873d115fc4e/go.mod h1:VJx/ZNGmClQDS5F6EmsGqK8j3jz1qJYZ6D9+MdAD+kw= +github.com/unrolled/secure v0.0.0-20190624173513-716474489ad3/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= +github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtaci/kcp-go v5.4.5+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= +go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= +go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= +go.dedis.ch/kyber/v3 v3.0.12/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= +go.dedis.ch/kyber/v3 v3.0.13/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= +go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= +go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= +go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 h1:oBcONsksxvpeodDrLjiMDaKHXKAVVfAydhe/792CE/o= +go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20170324220409-6c2325251549/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170325170518-afadfcc7779c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190912185636-87d9f09c5d89/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124202034-299f270db459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.1/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= +gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/guregu/null.v2 v2.1.2/go.mod h1:XORrx8tyS5ZDcyUboCIxQtta/Aujk/6pfWrn9Xe33mU= +gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= +gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= +gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= +gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170208141851-a3f3340b5840/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +helm.sh/helm/v3 v3.8.0 h1:vlQQDDQkrH4NECOFbGcwjjKyHL5Sa3xNLjMxXm7fMVo= +helm.sh/helm/v3 v3.8.0/go.mod h1:0nYPSuvuj8TTJDLRSAfbzGGbazPZsayaDpP8s9FfZT8= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= +k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E= +k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= +k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= +k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= +k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM= +k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.23.1 h1:vWGf8LcV9Pk/z5rdLmCiBDqE21ccbe930dzrtVMhw9g= +k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= +k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0= +k8s.io/cli-runtime v0.23.4 h1:C3AFQmo4TK4dlVPLOI62gtHEHu0OfA2Cp4UVRZ1JXns= +k8s.io/cli-runtime v0.23.4/go.mod h1:7KywUNTUibmHPqmpDFuRO1kc9RhsufHv2lkjCm2YZyM= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= +k8s.io/client-go v0.23.4 h1:YVWvPeerA2gpUudLelvsolzH7c2sFoXXR5wM/sWqNFU= +k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= +k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= +k8s.io/code-generator v0.23.4/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= +k8s.io/component-base v0.23.4 h1:SziYh48+QKxK+ykJ3Ejqd98XdZIseVBG7sBaNLPqy6M= +k8s.io/component-base v0.23.4/go.mod h1:8o3Gg8i2vnUXGPOwciiYlkSaZT+p+7gA9Scoz8y4W4E= +k8s.io/component-helpers v0.23.1/go.mod h1:ZK24U+2oXnBPcas2KolLigVVN9g5zOzaHLkHiQMFGr0= +k8s.io/component-helpers v0.23.4/go.mod h1:1Pl7L4zukZ054ElzRbvmZ1FJIU8roBXFOeRFu8zipa4= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kubectl v0.23.1/go.mod h1:Ui7dJKdUludF8yWAOSN7JZEkOuYixX5yF6E6NjoukKE= +k8s.io/kubectl v0.23.4 h1:mAa+zEOlyZieecEy+xSrhjkpMcukYyHWzcNdX28dzMY= +k8s.io/kubectl v0.23.4/go.mod h1:Dgb0Rvx/8JKS/C2EuvsNiQc6RZnX0SbHJVG3XUzH6ok= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/metrics v0.23.1/go.mod h1:qXvsM1KANrc+ZZeFwj6Phvf0NLiC+d3RwcsLcdGc+xs= +k8s.io/metrics v0.23.4/go.mod h1:cl6sY9BdVT3DubbpqnkPIKi6mn/F2ltkU4yH1tEJ3Bo= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +oras.land/oras-go v1.1.0 h1:tfWM1RT7PzUwWphqHU6ptPU3ZhwVnSw/9nEGf519rYg= +oras.land/oras-go v1.1.0/go.mod h1:1A7vR/0KknT2UkJVWh+xMi95I/AhK8ZrxrnUSmXN0bQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0= +sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= +sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= +sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= +sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY= +sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/integration-tests/smoke/cron_test.go b/integration-tests/smoke/cron_test.go index bed715070f0..41288369aee 100644 --- a/integration-tests/smoke/cron_test.go +++ b/integration-tests/smoke/cron_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -28,7 +26,7 @@ var _ = Describe("Cronjob suite @cron", func() { BeforeEach(func() { By("Deploying the environment", func() { e, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(nil), + environment.NewChainlinkConfig(nil, ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) diff --git a/integration-tests/smoke/flux_test.go b/integration-tests/smoke/flux_test.go index 14e06d19eb0..910388cf324 100644 --- a/integration-tests/smoke/flux_test.go +++ b/integration-tests/smoke/flux_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -42,7 +40,7 @@ var _ = Describe("Flux monitor suite @flux", func() { BeforeEach(func() { By("Deploying the environment", func() { e, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(environment.ChainlinkReplicas(3, nil)), + environment.NewChainlinkConfig(environment.ChainlinkReplicas(3, nil), ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) diff --git a/integration-tests/smoke/keeper_test.go b/integration-tests/smoke/keeper_test.go index de0d7985b09..a73c3fbabd5 100644 --- a/integration-tests/smoke/keeper_test.go +++ b/integration-tests/smoke/keeper_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -36,7 +34,7 @@ var _ = Describe("Keeper suite @keeper", func() { BeforeEach(func() { By("Deploying the environment", func() { env, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(environment.ChainlinkReplicas(6, nil)), + environment.NewChainlinkConfig(environment.ChainlinkReplicas(6, nil), ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) diff --git a/integration-tests/smoke/ocr_test.go b/integration-tests/smoke/ocr_test.go index 4a2f208750f..fde8e855b3e 100644 --- a/integration-tests/smoke/ocr_test.go +++ b/integration-tests/smoke/ocr_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -32,7 +30,7 @@ var _ = Describe("OCR Feed @ocr", func() { BeforeEach(func() { By("Deploying the environment", func() { env, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(environment.ChainlinkReplicas(6, nil)), + environment.NewChainlinkConfig(environment.ChainlinkReplicas(6, nil), ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) diff --git a/integration-tests/smoke/runlog_test.go b/integration-tests/smoke/runlog_test.go index c8638c8991a..7b3031ca954 100644 --- a/integration-tests/smoke/runlog_test.go +++ b/integration-tests/smoke/runlog_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -36,7 +34,7 @@ var _ = Describe("Direct request suite @runlog", func() { BeforeEach(func() { By("Deploying the environment", func() { e, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(environment.ChainlinkReplicas(3, nil)), + environment.NewChainlinkConfig(environment.ChainlinkReplicas(3, nil), ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) diff --git a/integration-tests/smoke/suite_test.go b/integration-tests/smoke/suite_test.go index 744a28eddb4..45fdd408301 100644 --- a/integration-tests/smoke/suite_test.go +++ b/integration-tests/smoke/suite_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test import ( diff --git a/integration-tests/smoke/vrf_test.go b/integration-tests/smoke/vrf_test.go index 9c458abfda0..6ab2ed587ef 100644 --- a/integration-tests/smoke/vrf_test.go +++ b/integration-tests/smoke/vrf_test.go @@ -1,5 +1,3 @@ -//go:build smoke - package smoke_test //revive:disable:dot-imports @@ -38,7 +36,7 @@ var _ = Describe("VRF suite @vrf", func() { BeforeEach(func() { By("Deploying the environment", func() { e, err = environment.DeployOrLoadEnvironment( - environment.NewChainlinkConfig(nil), + environment.NewChainlinkConfig(nil, ""), tools.ChartsRoot, ) Expect(err).ShouldNot(HaveOccurred()) @@ -152,6 +150,7 @@ var _ = Describe("VRF suite @vrf", func() { By("Printing gas stats", func() { nets.Default.GasStats().PrintStats() }) + By("Tearing down the environment", func() { err = actions.TeardownSuite(e, nets, utils.ProjectRoot, nil) Expect(err).ShouldNot(HaveOccurred()) diff --git a/operator_ui/@types/core/store/models.d.ts b/operator_ui/@types/core/store/models.d.ts index 369dc202bf2..3e35c1955a5 100644 --- a/operator_ui/@types/core/store/models.d.ts +++ b/operator_ui/@types/core/store/models.d.ts @@ -77,6 +77,7 @@ declare module 'core/store/models' { wsURL: string createdAt: time.Time updatedAt: time.Time + state: string } // We really need to change the API for this. It not only returns levels but diff --git a/operator_ui/src/components/Form/FeedsManagerForm.tsx b/operator_ui/src/components/Form/FeedsManagerForm.tsx index 2bf54bead45..58635119ffc 100644 --- a/operator_ui/src/components/Form/FeedsManagerForm.tsx +++ b/operator_ui/src/components/Form/FeedsManagerForm.tsx @@ -20,6 +20,10 @@ const jobTypes = [ label: 'OCR', value: 'OCR', }, + { + label: 'OCR2', + value: 'OCR2', + }, ] export type FormValues = { diff --git a/operator_ui/src/pages/Chains/NodeRow.tsx b/operator_ui/src/pages/Chains/NodeRow.tsx index 8e0f99e5f34..c6ae73629ba 100644 --- a/operator_ui/src/pages/Chains/NodeRow.tsx +++ b/operator_ui/src/pages/Chains/NodeRow.tsx @@ -31,6 +31,8 @@ export const NodeRow = withStyles(tableStyles)(({ node, classes }: Props) => { {createdAt} + + {node.attributes.state} ) }) diff --git a/operator_ui/src/pages/Chains/NodesList.tsx b/operator_ui/src/pages/Chains/NodesList.tsx index 24a47d731bb..6376a989fa6 100644 --- a/operator_ui/src/pages/Chains/NodesList.tsx +++ b/operator_ui/src/pages/Chains/NodesList.tsx @@ -44,6 +44,12 @@ const List = ({ nodes, nodeFilter }: Props) => { Created + + + + State + + diff --git a/operator_ui/src/screens/EditFeedsManager/EditFeedsManagerScreen.test.tsx b/operator_ui/src/screens/EditFeedsManager/EditFeedsManagerScreen.test.tsx index bfc836458cb..7eb106ee042 100644 --- a/operator_ui/src/screens/EditFeedsManager/EditFeedsManagerScreen.test.tsx +++ b/operator_ui/src/screens/EditFeedsManager/EditFeedsManagerScreen.test.tsx @@ -170,7 +170,7 @@ describe('EditFeedsManagerScreen', () => { userEvent.clear(publicKeyInput) userEvent.type(publicKeyInput, '22222') - userEvent.click(getByRole('checkbox', { name: /ocr/i })) + userEvent.click(getByRole('checkbox', { name: 'OCR' })) userEvent.click(getByRole('button', { name: /submit/i })) diff --git a/operator_ui/src/screens/FeedsManager/FeedsManagerCard.tsx b/operator_ui/src/screens/FeedsManager/FeedsManagerCard.tsx index 3d5a1295920..2ce174fafe9 100644 --- a/operator_ui/src/screens/FeedsManager/FeedsManagerCard.tsx +++ b/operator_ui/src/screens/FeedsManager/FeedsManagerCard.tsx @@ -101,6 +101,8 @@ export const FeedsManagerCard = ({ manager }: Props) => { return 'Flux Monitor' case 'OCR': return 'OCR' + case 'OCR2': + return 'OCR2' } }) .join(', ') diff --git a/operator_ui/src/screens/Job/JobView.tsx b/operator_ui/src/screens/Job/JobView.tsx index 1dd461b67dc..9dcb69c5de1 100644 --- a/operator_ui/src/screens/Job/JobView.tsx +++ b/operator_ui/src/screens/Job/JobView.tsx @@ -71,18 +71,19 @@ const JOB_PAYLOAD__SPEC = gql` contractID contractConfigConfirmations contractConfigTrackerPollInterval - juelsPerFeeCoinSource ocrKeyBundleID monitoringEndpoint p2pBootstrapPeers relay relayConfig transmitterID + pluginType + pluginConfig } ... on VRFSpec { evmChainID coordinatorAddress - fromAddress + fromAddresses minIncomingConfirmations minIncomingConfirmationsEnv pollPeriod diff --git a/operator_ui/src/screens/Job/generateJobDefinition.test.ts b/operator_ui/src/screens/Job/generateJobDefinition.test.ts index 39a75be4876..850608bec5c 100644 --- a/operator_ui/src/screens/Job/generateJobDefinition.test.ts +++ b/operator_ui/src/screens/Job/generateJobDefinition.test.ts @@ -344,7 +344,6 @@ observationTimeout = "10s" contractID: '0x1469877c88F19E273EFC7Ef3C9D944574583B8a0', contractConfigConfirmations: 3, contractConfigTrackerPollInterval: '1m0s', - juelsPerFeeCoinSource: '1000000000', ocrKeyBundleID: '4ee612467c3caea7bdab57ab62937adfc4d195516c30139a737f85098b35d9af', monitoringEndpoint: 'https://monitoring.endpoint', @@ -355,6 +354,10 @@ observationTimeout = "10s" relayConfig: { chainID: 1337, }, + pluginType: 'median', + pluginConfig: { + juelsPerFeeCoinSource: '1000000000', + }, transmitterID: '0x01010CaB43e77116c95745D219af1069fE050d7A', }, runs: { @@ -376,13 +379,13 @@ blockchainTimeout = "20s" contractID = "0x1469877c88F19E273EFC7Ef3C9D944574583B8a0" contractConfigConfirmations = 3 contractConfigTrackerPollInterval = "1m0s" -juelsPerFeeCoinSource = "1000000000" ocrKeyBundleID = "4ee612467c3caea7bdab57ab62937adfc4d195516c30139a737f85098b35d9af" monitoringEndpoint = "https://monitoring.endpoint" p2pBootstrapPeers = [ "/ip4/139.59.41.32/tcp/12000/p2p/12D3KooWGKhStcrvCr5RBYKaSRNX4ojrxHcmpJuFmHWenT6aAQAY" ] relay = "evm" +pluginType = "median" transmitterID = "0x01010CaB43e77116c95745D219af1069fE050d7A" observationSource = """ fetch [type=http method=POST url="http://localhost:8001" requestData="{\\\\"hi\\\\": \\\\"hello\\\\"}"]; @@ -393,6 +396,9 @@ observationSource = """ [relayConfig] chainID = 1_337 + +[pluginConfig] +juelsPerFeeCoinSource = "1000000000" ` const output = generateJobDefinition(job) @@ -412,7 +418,7 @@ chainID = 1_337 __typename: 'VRFSpec', coordinatorAddress: '0x0000000000000000000000000000000000000000', evmChainID: '42', - fromAddress: '', + fromAddresses: ['0x3cCad4715152693fE3BC4460591e3D3Fbd071b42'], minIncomingConfirmations: 6, minIncomingConfirmationsEnv: false, pollPeriod: '10s', @@ -431,7 +437,7 @@ name = "vrf job" externalJobID = "00000000-0000-0000-0000-0000000000001" coordinatorAddress = "0x0000000000000000000000000000000000000000" evmChainID = "42" -fromAddress = "" +fromAddresses = [ "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42" ] minIncomingConfirmations = 6 pollPeriod = "10s" publicKey = "0x92594ee04c179eb7d439ff1baacd98b81a7d7a6ed55c86ca428fa025bd9c914301" diff --git a/operator_ui/src/screens/Job/generateJobDefinition.ts b/operator_ui/src/screens/Job/generateJobDefinition.ts index e2767ebd62f..d3ae7e04944 100644 --- a/operator_ui/src/screens/Job/generateJobDefinition.ts +++ b/operator_ui/src/screens/Job/generateJobDefinition.ts @@ -177,12 +177,13 @@ export const generateJobDefinition = ( 'contractID', 'contractConfigConfirmations', 'contractConfigTrackerPollInterval', - 'juelsPerFeeCoinSource', 'ocrKeyBundleID', 'monitoringEndpoint', 'p2pBootstrapPeers', 'relay', 'relayConfig', + 'pluginType', + 'pluginConfig', ), // We need to call 'extractSpecFields' again here so we get the spec // fields displaying in alphabetical order. @@ -198,7 +199,7 @@ export const generateJobDefinition = ( job.spec, 'coordinatorAddress', 'evmChainID', - 'fromAddress', + 'fromAddresses', 'minIncomingConfirmations', 'pollPeriod', 'publicKey', diff --git a/operator_ui/src/screens/JobProposal/JobProposalCard.test.tsx b/operator_ui/src/screens/JobProposal/JobProposalCard.test.tsx index 1fdc9305842..2e0484c425b 100644 --- a/operator_ui/src/screens/JobProposal/JobProposalCard.test.tsx +++ b/operator_ui/src/screens/JobProposal/JobProposalCard.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react' -import { render, screen } from 'support/test-utils' +import { Route } from 'react-router-dom' +import { renderWithRouter, screen } from 'support/test-utils' import { JobProposalCard } from './JobProposalCard' import { @@ -12,7 +13,21 @@ const { queryAllByText, queryByText } = screen describe('JobProposalCard', () => { function renderComponent(proposal: JobProposalPayloadFields) { - render() + renderWithRouter( + <> + + + + + +
+
+ + + Run Page + + , + ) } it('renders a pending job proposal card', () => { @@ -33,6 +48,7 @@ describe('JobProposalCard', () => { it('renders a pending job proposal card with an approved spec', () => { const proposal = buildJobProposal({ externalJobID: '10000000-0000-0000-0000-000000000001', + jobID: '1', specs: [ buildJobProposalSpec({ status: 'APPROVED', diff --git a/operator_ui/src/screens/JobProposal/JobProposalCard.tsx b/operator_ui/src/screens/JobProposal/JobProposalCard.tsx index 4a09eab1b85..a4656e7ea3c 100644 --- a/operator_ui/src/screens/JobProposal/JobProposalCard.tsx +++ b/operator_ui/src/screens/JobProposal/JobProposalCard.tsx @@ -7,6 +7,7 @@ import { DetailsCardItemTitle, DetailsCardItemValue, } from 'src/components/Cards/DetailsCard' +import Link from 'src/components/Link' import titleize from 'src/utils/titleize' interface Props { @@ -29,7 +30,15 @@ export const JobProposalCard = ({ proposal }: Props) => { - + {proposal.jobID && proposal.externalJobID ? ( + + + {proposal.externalJobID} + + + ) : ( + + )} diff --git a/operator_ui/src/screens/JobProposal/JobProposalView.tsx b/operator_ui/src/screens/JobProposal/JobProposalView.tsx index 6a0d89797a6..17dd73c9396 100644 --- a/operator_ui/src/screens/JobProposal/JobProposalView.tsx +++ b/operator_ui/src/screens/JobProposal/JobProposalView.tsx @@ -17,6 +17,7 @@ export const JOB_PROPOSAL_PAYLOAD_FIELDS = gql` id externalJobID remoteUUID + jobID specs { ...JobProposal_SpecsFields } diff --git a/operator_ui/src/screens/Node/NodeView.tsx b/operator_ui/src/screens/Node/NodeView.tsx index 050ab0537cc..233891fe948 100644 --- a/operator_ui/src/screens/Node/NodeView.tsx +++ b/operator_ui/src/screens/Node/NodeView.tsx @@ -19,6 +19,7 @@ export const NODE_PAYLOAD_FIELDS = gql` httpURL wsURL createdAt + state } ` diff --git a/operator_ui/src/screens/Nodes/NodeRow.tsx b/operator_ui/src/screens/Nodes/NodeRow.tsx index 9082e1d88b3..38b37b9edfe 100644 --- a/operator_ui/src/screens/Nodes/NodeRow.tsx +++ b/operator_ui/src/screens/Nodes/NodeRow.tsx @@ -26,6 +26,7 @@ export const NodeRow = withStyles(tableStyles)(({ node, classes }: Props) => { {node.createdAt} + {node.state} ) }) diff --git a/operator_ui/src/screens/Nodes/NodesView.tsx b/operator_ui/src/screens/Nodes/NodesView.tsx index b75342e50f4..25fafe11e25 100644 --- a/operator_ui/src/screens/Nodes/NodesView.tsx +++ b/operator_ui/src/screens/Nodes/NodesView.tsx @@ -25,6 +25,7 @@ export const NODES_PAYLOAD__RESULTS_FIELDS = gql` } name createdAt + state } ` @@ -97,6 +98,7 @@ export const NodesView: React.FC = ({ Name EVM Chain ID Created + State diff --git a/operator_ui/support/factories/gql/fetchNode.ts b/operator_ui/support/factories/gql/fetchNode.ts index 1bea747173a..fa63621b5fb 100644 --- a/operator_ui/support/factories/gql/fetchNode.ts +++ b/operator_ui/support/factories/gql/fetchNode.ts @@ -16,6 +16,7 @@ export function buildNodePayloadFields( chain: { id: '42', }, + state: '', ...overrides, } } diff --git a/operator_ui/support/factories/gql/fetchNodes.ts b/operator_ui/support/factories/gql/fetchNodes.ts index a54f6720f3a..b419c83d98b 100644 --- a/operator_ui/support/factories/gql/fetchNodes.ts +++ b/operator_ui/support/factories/gql/fetchNodes.ts @@ -14,6 +14,7 @@ export function buildNode( id: '42', }, createdAt: minuteAgo, + state: '', ...overrides, } } diff --git a/tools/bin/go_core_race_tests b/tools/bin/go_core_race_tests index a7ca3b29ac6..4f14ec1cde5 100755 --- a/tools/bin/go_core_race_tests +++ b/tools/bin/go_core_race_tests @@ -2,8 +2,7 @@ set -ex GO_LDFLAGS=$(bash tools/bin/ldflags) -# remove -short for complete coverage -GORACE="log_path=$PWD/race" LOG_LEVEL=panic go test -race -ldflags "$GO_LDFLAGS" -short -shuffle on -timeout 30s -count 10 -p 4 ./core/... | tee ./output.txt +GORACE="log_path=$PWD/race" LOG_LEVEL=panic go test -race -ldflags "$GO_LDFLAGS" -shuffle on -timeout 30s -count 10 -p 4 ./core/... | tee ./output.txt EXITCODE=${PIPESTATUS[0]} # Fail if any race logs are present. if ls race.* &>/dev/null diff --git a/tools/clroot/.gitignore b/tools/clroot/.gitignore index 2ec8d8eaf4b..c2c46129ba7 100644 --- a/tools/clroot/.gitignore +++ b/tools/clroot/.gitignore @@ -1,5 +1,5 @@ db.sqlite3 -log.jsonl +chainlink_debug.log cookie secret chainlink.lock